Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    Send daily random photos from an S3 bucket using AWS Lambda – CodeWithSwiz 25

    We did it! We finished the experiment from a few weeks ago and The Girl can stop making jokes that I stole her present πŸŽ‰

    Here's the plan:

    1. Buy cute IoT gimmick for Valentine's Day βœ…
    2. Reverse engineer its API βœ…
    3. Build serverless Lambda that sends a picture βœ…πŸ‘†
    4. Move secrets to secrets manager βŒ›οΈ
    5. Read photos from S3 βŒ›οΈ
    6. Send base64 encoded photos βŒ›οΈ
    7. Run daily βŒ›οΈ

    The Girl was getting upset with me [name|Friend]. Waiting for those last 3 steps since February while I focused on Serverless Handbook.

    She waits no more! It's live

    Lambda triggered on a daily cron
    Lambda triggered on a daily cron

    You can see the full code on GitHub. Here's what we built:

    Secrets in secrets manager

    Never deploy code with secret API keys in the files. You will get hacked and your data will get leaked.

    And when we're talking anonymous strangers from the internet can send dick pics to your girl ... nu-uh. All identifying info and API tokens went into the AWS Secrets Manager.

    Accessed in code like this:

    // src/secrets.ts
    
    import { SecretsManager } from "aws-sdk";
    
    const ssm = new SecretsManager({
      region: "us-east-1", // make sure this matches your region
    });
    
    export async function getBearerToken() {
      const secret = await ssm
        .getSecretValue({ SecretId: "loveboxBearerToken" })
        .promise();
    
      const { BEARER_TOKEN } = JSON.parse(secret?.SecretString || "");
    
      return BEARER_TOKEN;
    }
    

    Grab the SecretsManager, instantiate with our region, write helper function to grab the API token. It talks to secrets secrets manager, retrieves the loveboxBearerToken secret, parses the JSON response and returns what we need.

    I added my other secrets later.

    And we remembered to give our Lambda permissions to read secrets:

    # serverless.yml
    provider:
      # ...
      iamRoleStatements:
        - Effect: "Allow"
          Action:
            - "secretsmanager:GetSecretValue"
          Resource: "arn:aws:secretsmanager:${self:provider.region}:*"
    

    Borrowed all this from the Serverless Handbook chapter on secrets. No point in memorizing what you can look up πŸ˜‡

    Using secrets for the GraphQLClient

    We need the bearer token to authorize our GraphQL client with the API. Means we have to instantiate with an async function.

    But talking to SecretsManager is an API call itself and that could make our Lambda slow. The answer is memoization.

    let graphQLClient: null | GraphQLClient = null;
    
    async function createGraphQLClient() {
      if (graphQLClient === null) {
        const token = await getBearerToken();
    
        graphQLClient = new GraphQLClient(
          "https://app-api.loveboxlove.com/v1/graphql",
          {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          }
        );
      }
    
      return graphQLClient;
    }
    

    We create a global variable graphQLClient. When it's defined createGraphQLClient returns the client, when it isn't it first instantiates our client after reading the token from secrets.

    This lets us skip the secrets call when our Lambda gets reused ✌️

    Read photos from S3

    Reading photos from S3 happens in two steps:

    1. Get list of photos in bucket
    2. Read random photo

    List photos

    We start by giving our lambda permissions to read S3.

    # serverless.yml
    
    provider:
      # ...
      iamRoleStatements:
        # ...
        - Effect: "Allow"
          Action:
            - "s3:listObjects"
          Resource: "arn:aws:s3:::lovebox-stash/*"
    

    Yes, the S3 bucket is called lovebox-stash.

    Then we create a helper function that grabs a list of objects from S3. This part was uneventful, but we had to make sure it works.

    // src/pictures.ts
    
    import { S3 } from "aws-sdk";
    
    const s3 = new S3({
      apiVersion: "2006-03-01",
    });
    
    async function listPictures() {
      const list = await s3
        .listObjects({
          Bucket: "lovebox-stash",
        })
        .promise();
    
      return list.Contents;
    }
    

    Instantiate an S3 client, keep it shared between Lambda calls, make a request to S3 to list every object in the bucket.

    Read random photo

    Grabbing a random photo is a matter of maths and fetching an S3 object. Like this:

    // src/pictures.ts
    
    export async function getPicture() {
      const pictures = await listPictures();
    
      if (pictures) {
        const randomPic = pictures[Math.floor(Math.random() * pictures.length)];
    
        if (randomPic.Key) {
          const imageData = await s3
            .getObject({
              Bucket: "lovebox-stash",
              Key: randomPic.Key,
            })
            .promise();
    
          return imageData;
        }
      }
    
      return null;
    }
    

    Grab list of photos, pick at random, read data. Ifs help with error handling.

    Send base64 encoded photos

    The final sendPicture function looks like this:

    async function sendPicture(picture: S3.GetObjectOutput) {
      const base64 = `data:${picture.ContentType};base64,${picture.Body?.toString(
        "base64"
      )}`;
      const client = await createGraphQLClient();
      const { recipient, deviceId } = await getRecipient();
    
      const res = await client.request(
        // query,
        {
          base64,
          recipient,
          contentType: ["Image"],
          options: {
            framesBase64: null,
            deviceId,
          },
        }
      );
      console.log(res);
    }
    

    Get random photo, encode in base64, add the mime type. Create authorized GraphQL client, get recipient secrets, send as variables to the GraphQL query.

    And you get a happy Swiz

    Yay the hackery works

    Run daily

    This part's easy πŸ‘‰ add cron config to serverless.yml

    # serverless.yml
    
    functions:
        sendNote:
            handler: dist/lovebox.sendNote
            events:
                - schedule: rate(1 day)
    

    Run daily, at a time of your choosing.

    Cheers,
    ~Swizec

    PS: the Serverless Handbook paperback is now $36 on Amazon, not sure why but check it out if you're curious

    Published on April 6th, 2021 in Uncategorized

    Did you enjoy this article?

    Continue reading about Send daily random photos from an S3 bucket using AWS Lambda – CodeWithSwiz 25

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help πŸ‘‰ swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers πŸ‘‰ ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❀️

    Created by Swizec with ❀️