Skip to main content

Resource Binding

Access the resources in your app in a secure and typesafe way.


Overview

Resource Binding allows you to connect your functions with your infrastructure. This is done in two steps:

  1. Bind a resource to the functions in your infrastructure code through the bind prop.
  2. Use the @serverless-stack/node package to access the resource in your function.

Quick start

To see how Resource Binding works, we are going to create an S3 bucket and bind it to a Lambda function.

Follow along by creating the Minimal TypeScript starter by running npx create-sst@latest > minimal > minimal/typescript-starter. Alternatively, you can refer to this example repo that's based on the same template.

  1. To create a new bucket, open up stacks/MyStack.ts and add a Bucket construct below the API.

    stacks/MyStack.ts
    const bucket = new Bucket(stack, "myFiles");

    You'll also need to import Bucket at the top.

    import { Bucket } from "@serverless-stack/resources";
  2. Then, bind the bucket to the api.

    stacks/MyStack.ts
    api.bind([bucket]);
  3. Now we can access the bucket's name in our API using the Bucket helper. Change services/functions/lambda.ts to:

    services/functions/lambda.ts
    import { APIGatewayProxyHandlerV2 } from "aws-lambda";
    import { Bucket } from "@serverless-stack/node/bucket";
    import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
    const s3 = new S3Client({});

    export const handler: APIGatewayProxyHandlerV2 = async (event) => {
    // Upload a file to the bucket
    await s3.send(
    new PutObjectCommand({
    Bucket: Bucket.myFiles.bucketName,
    Key: "greeting.txt",
    Body: "Hello world!",
    })
    );

    return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `File uploaded`,
    };
    };

    You'll also need to install the node package in the services/ directory.

    npm install --save @serverless-stack/node

    And the AWS SDK for this example.

    npm install --save @aws-sdk/client-s3

    That's it!


Features

Let's take a look at some of the key features of Resource Binding, and how it makes building apps fun and easy again.


Typesafety

In the above example, the Bucket object that's imported from @serverless-stack/node/bucket is typesafe. Your editor should be able to autocomplete the bucket name myFiles, as well as its property bucketName.

resource binding typesafe

Behind the scenes

Let's take a look at how this is all wired up.

  1. First, the @serverless-stack/node/table package predefines an interface.

    export interface BucketResources {}
  2. When SST builds the app, it generates a type file and adds the bucket name to the BucketResources interface.

    node_modules/@types/serverless-stack__node/Bucket-myFiles.d.ts
    import "@serverless-stack/node/bucket";
    declare module "@serverless-stack/node/bucket" {
    export interface BucketResources {
    myFiles: {
    bucketName: string;
    };
    }
    }

    This type file then gets appended to index.d.ts.

    node_modules/@types/@serverless-stack__node/index.d.ts
    export * from "./Bucket-myFiles";
  3. So when the Bucket object is imported from @serverless-stack/node/bucket, it has the type BucketResources.


Error handling

If you reference a resource that doesn't exist in your SST app, or hasn't been bound to the function, you'll get a runtime error.

For example, if you forget to bind the bucket to the API, you'll get the following error when the function is invoked.

Cannot use Bucket.myFiles. Please make sure it is bound to this function.

Testing

When testing your code, you can use the sst bind CLI to bind the resources to your tests.

sst bind -- vitest run

This allows the @serverless-stack/node helper library to work as if it was running inside a Lambda function.

Read more about testing and learn about the sst bind CLI.


Permissions

When a resource is bound to a Lambda function, the permissions to access that resource are automatically granted to the function.

api.bind([bucket]);

Here, by binding the bucket to the api, the API routes are able to perform file download, upload, delete, and other actions against the bucket.

Behind the scenes

An IAM policy is added to the Lambda function's role, allowing it to perform s3:* actions on the S3 bucket's ARN.

The IAM policy statement looks like:

{
"Action": "s3:*",
"Resource": ["arn:aws:s3:::{BUCKET_NAME}", "arn:aws:s3:::{BUCKET_NAME}/*"],
"Effect": "Allow",
}

Construct support

Resource Binding works across all SST constructs. Here are a few more examples.

  • Getting the Next.js URL

    import { NextjsSite } from "@serverless-stack/node/site";

    NextjsSite.myFrontend.url;
  • DynamoDB table name

    import { Table } from "@serverless-stack/node/table";

    Table.myTable.tableName;
  • RDS cluster data

    import { RDS } from "@serverless-stack/node/rds";

    RDS.myDB.clusterArn;
    RDS.myDB.secretArn;
    RDS.myDB.defaultDatabaseName;

See the full list of helpers.


Binding other resources

So far we've seen how Resource Binding allows your functions to access values from other SST constructs. But there are 2 other types of values you might want to access in your functions.

  1. Secrets, because you can't define the value of the secrets in your functions.
  2. Values from non-SST constructs, for example static values or values from CDK constructs.

For these you can use Config. Here are a couple of examples.


Binding secrets

To bind a secret to our function, start by creating a Config.Secret construct.

const STRIPE_KEY = new Config.Secret(stack, "STRIPE_KEY");

And continuing with our example, bind it to the api.

api.bind([STRIPE_KEY]);

Now set the secret value using the SST CLI.

npx sst secrets set STRIPE_KEY sk_test_abc123

And access the value in the function.

import { Config } from "@serverless-stack/node/config";

Config.STRIPE_KEY;

You can read more about secrets.


Binding CDK resources

Assuming you have an ECS cluster in your app and you need to pass the cluster name to your function.

Since SST doesn't have a construct for ECS, create a Config.Parameter construct with the cluster name being the value.

const cluster = new ecs.Cluster(stack, "myCluster");

const MY_CLUSTER_NAME = new Config.Parameter(stack, "MY_CLUSTER_NAME", {
value: cluster.clusterName,
});

Then bind it to the api from our example.

api.bind([MY_CLUSTER_NAME]);

And you can access the value in your function.

import { Config } from "@serverless-stack/node/config";

Config.MY_CLUSTER_NAME;

How it works

When a resource is bound to a Lambda function, the resource values are stored as environment variables for the function. In our example, the bucket name is stored as a Lambda environment variable named SST_Bucket_bucketName_myBucket.

At runtime, the @serverless-stack/node/bucket package reads the value process.env.SST_Bucket_bucketName_MyBucket and makes it accessible via Bucket.myBucket.bucketName.

SST also stores a copy of the bucket name in AWS SSM. In this case, an SSM parameter of the type String is created with the name /sst/{appName}/{stageName}/Bucket/MyBucket/bucketName, where {appName} is the name of your SST app, and {stageName} is the stage. The parameter value is the name of the bucket stored in plain text.

Storing the bucket name in SSM might seem redundant. But it provides a convenient way to fetch all the bound resources in your application. This can be extremely useful for testing. This isn't possible when using Lambda environment variables and we are going to see why.


Binding sensitive values

When binding resources that contain sensitive values, placeholders are stored in the Lambda environment variables. The actual values are stored inside SSM. At runtime, the values are fetched from SSM when the Lambda container first boots up. And the values are cached for subsequent invocations. This is similar to how Config.Secret works.


Cost

Resource Binding values are stored in AWS SSM with the Standard Parameter type and Standard Throughput. This makes it free to use in your SST apps.

FAQ

Here are some frequently asked questions about Resource Binding.


Resource Binding or Lambda environment variables?

Prior to Resource Binding, people used Lambda environment variables to pass information to their functions.

Aside from the lack of typesafety and error handling, Lambda environment variables have a few drawbacks. Imagine you have a Lambda function that looks like this.

services/users/updated.ts
export const handler = async () => {
if (process.env.TOPIC_NAME !== "UserUpdated") {
return;
}

// ...
};

Where TOPIC_NAME is stored as a Lambda environment variable. You'll need to handle the following:

  1. When testing this function, locally or in your CI, you need to figure out the value for TOPIC_NAME and set it as an environment variable.

  2. In addition, imagine you have another function that also has a TOPIC_NAME Lambda environment variable, but with a different value.

    services/billing/charged.ts
    export const handler = async () => {
    if (process.env.TOPIC_NAME !== "InvoiceCharged") {
    return;
    }

    // ...
    };

    What should the TOPIC_NAME be in your tests?

With Resource Binding, the value for the topic name is also stored in SSM. When running tests, SST can automatically fetch this from SSM using the sst bind CLI.


Does this make my Lambda functions slower?

No. The resource values are stored as environment variables for the function. At runtime, reading from environment variables is instantaneous.

For sensitive values, the values are stored in AWS SSM. When the Lambda container first boots up, the values are fetched from SSM and are cached for subsequent invocations.


What if I'm not using Node.js runtime?

For non-Node.js runtimes, you can continue to use Lambda environment variables.

If you want to use Resource Binding, you would need to read the bound values from the Lambda environment variable and AWS SSM directly. Refer to the @serverless-stack/node package to see how it is done in Node.js.