Swizec Teller - a geek with a hatswizec.com

    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:

    Serverless Handbook for Frontend Engineers – free chapter

    Dive modern backend. Understand any backend.

    Serverless Handbook taught me high-leveled topics. I don't like recipe courses and these chapters helped me to feel like I'm not a total noob anymore.

    The hand-drawn diagrams and high-leveled descriptions gave me the feeling that I don't have any critical "knowledge gaps" anymore.

    ~ Marek C, engineer

    Start with a free chapter and email crash course ❤️

    Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    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

    Did you enjoy this article?

    Published on April 6th, 2021 in

    Want to dive into serverless? Not sure where to begin?

    Serverless Handbook was designed for people like you getting into backend programming.

    360 pages, 19 chapters, 6 full projects, hand-drawn diagrams, beautiful chapter art, best-looking cover in tech. ✌️

    Serverless Handbook on your bookshelf
    Serverless Handbook on your bookshelf

    Learn how to choose the right database, write cloud functions, think about scalability, gain the architecture mindsets for robust systems, and more.

    Leave your email to start with a free chapter and email crash course 👇

    Serverless Handbook for Frontend Engineers – free chapter

    Dive modern backend. Understand any backend.

    Serverless Handbook taught me high-leveled topics. I don't like recipe courses and these chapters helped me to feel like I'm not a total noob anymore.

    The hand-drawn diagrams and high-leveled descriptions gave me the feeling that I don't have any critical "knowledge gaps" anymore.

    ~ Marek C, engineer

    Start with a free chapter and email crash course ❤️

    Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

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

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

    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 bySwizecwith ❤️