Skip to content

Examples

A collection of example apps for reference.

Below are a collection of example SST apps. These are available in the examples/ directory of the repo.

The descriptions for these examples are generated using the comments in the sst.config.ts of the app.

Contributing

To contribute an example or to edit one, submit a PR to the repo. Make sure to document the sst.config.ts in your example.


API Gateway auth

Enable IAM and JWT authorizers for API Gateway routes.

sst.config.ts
const api = new sst.aws.ApiGatewayV2("MyApi", {
domain: {
name: "api.ion.sst.sh",
path: "v1",
},
});
api.route("GET /", {
handler: "route.handler",
});
api.route("GET /foo", "route.handler", { auth: { iam: true } });
api.route("GET /bar", "route.handler", {
auth: {
jwt: {
issuer:
"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_Rq4d8zILG",
audiences: ["user@example.com"],
},
},
});
api.route("$default", "route.handler");
return {
api: api.url,
};

View the full example.


Bucket policy

Create an S3 bucket and transform its bucket policy.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket", {
transform: {
policy: (args) => {
// use $jsonParse and $jsonStringify helper functions to manipulate JSON strings
// containing Output values from components
args.policy = $jsonParse(args.policy).apply((policy) => {
policy.Statement.push({
Effect: "Allow",
Principal: { Service: "ses.amazonaws.com" },
Action: "s3:PutObject",
Resource: $interpolate`arn:aws:s3:::${args.bucket}/*`,
});
return $jsonStringify(policy);
});
},
},
});
return {
bucket: bucket.name,
};

View the full example.


Bucket queue notifications

Create an S3 bucket and subscribe to its events with an SQS queue.

sst.config.ts
const queue = new sst.aws.Queue("MyQueue");
queue.subscribe("subscriber.handler");
const bucket = new sst.aws.Bucket("MyBucket");
bucket.subscribeQueue(queue.arn, {
events: ["s3:ObjectCreated:*"],
});
return {
bucket: bucket.name,
queue: queue.url,
};

View the full example.


Bucket notifications

Create an S3 bucket and subscribe to its events with a function.

sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
bucket.subscribe("subscriber.handler", {
events: ["s3:ObjectCreated:*"],
});
return {
bucket: bucket.name,
};

View the full example.


Bucket topic notifications

Create an S3 bucket and subscribe to its events with an SNS topic.

sst.config.ts
const topic = new sst.aws.SnsTopic("MyTopic");
topic.subscribe("subscriber.handler");
const bucket = new sst.aws.Bucket("MyBucket");
bucket.subscribeTopic(topic.arn, {
events: ["s3:ObjectCreated:*"],
});
return {
bucket: bucket.name,
topic: topic.name,
};

View the full example.


Subscribe to queues

Create an SQS queue, subscribe to it, and publish to it from a function.

sst.config.ts
// create dead letter queue
const dlq = new sst.aws.Queue("DeadLetterQueue");
dlq.subscribe("subscriber.dlq");
// create main queue
const queue = new sst.aws.Queue("MyQueue", {
dlq: dlq.arn,
});
queue.subscribe("subscriber.main");
const app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [queue],
url: true,
});
return {
app: app.url,
queue: queue.url,
dlq: dlq.url,
};

View the full example.


DynamoDB streams

Create a DynamoDB table, enable streams, and subscribe to it with a function.

sst.config.ts
const table = new sst.aws.Dynamo("MyTable", {
fields: {
id: "string",
},
primaryIndex: { hashKey: "id" },
stream: "new-and-old-images",
});
table.subscribe("subscriber.handler", {
filters: [
{
dynamodb: {
NewImage: {
message: {
S: ["Hello"],
},
},
},
},
],
});
const app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [table],
url: true,
});
return {
app: app.url,
table: table.name,
};

View the full example.


FFmpeg in Lambda

Uses FFmpeg to process videos. In this example, it takes a clip.mp4 and grabs a single frame from it.

We use the ffmpeg-static package that contains pre-built binaries for all architectures.

index.ts
import ffmpeg from "ffmpeg-static";

We can use this to spawn a child process and run FFmpeg.

index.ts
spawnSync(ffmpeg, ffmpegParams, { stdio: "pipe" });

We don’t need a layer when we deploy this because SST will use the right binary for the target Lambda architecture; including arm64.

sst.config.ts
{
nodejs: { install: ["ffmpeg-static"] }
}

All this is handled by nodejs.install.

sst.config.ts
const func = new sst.aws.Function("MyFunction", {
url: true,
memory: "2 GB",
timeout: "15 minutes",
handler: "index.handler",
copyFiles: [{ from: "clip.mp4" }],
nodejs: { install: ["ffmpeg-static"] },
});
return {
url: func.url,
};

View the full example.


IAM permissions boundaries

Use permissions boundaries to set the maximum permissions for all IAM roles that’ll be created in your app.

In this example, the Function has the s3:ListAllMyBuckets and sqs:ListQueues permissions. However, we create a permissions boundary that only allows s3:ListAllMyBuckets. And we apply it to all Roles in the app using the global $transform.

As a result, the Function is only allowed to list S3 buckets. If you open the deployed URL, you’ll see that the SQS list call fails.

Learn more about AWS IAM permissions boundaries.

sst.config.ts
// Create a permission boundary
const permissionsBoundary = new aws.iam.Policy("MyPermissionsBoundary", {
policy: aws.iam.getPolicyDocumentOutput({
statements: [
{
actions: ["s3:ListAllMyBuckets"],
resources: ["*"],
},
],
}).json,
});
// Apply the boundary to all roles
$transform(aws.iam.Role, (args) => {
args.permissionsBoundary = permissionsBoundary;
});
// The boundary automatically applies to this Function's role
const app = new sst.aws.Function("MyApp", {
handler: "index.handler",
permissions: [
{
actions: ["s3:ListAllMyBuckets", "sqs:ListQueues"],
resources: ["*"],
},
],
url: true,
});
return {
app: app.url,
};

View the full example.


Current AWS account

You can use the aws.getXXXXOutput() provider functions to get info about the current AWS account. Learn more about provider functions.

sst.config.ts
return {
region: aws.getRegionOutput().name,
account: aws.getCallerIdentityOutput({}).accountId,
};

View the full example.


Kinesis streams

Create a Kinesis stream, and subscribe to it with a function.

sst.config.ts
const stream = new sst.aws.KinesisStream("MyStream");
// Create a function subscribing to all events
stream.subscribe("subscriber.all");
// Create a function subscribing to events of `bar` type
stream.subscribe("subscriber.filtered", {
filters: [
{
data: {
type: ["bar"],
},
},
],
});
const app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [stream],
url: true,
});
return {
app: app.url,
stream: stream.name,
};

View the full example.


AWS multi-region

To deploy resources to multiple AWS regions, you can create a new provider for the region you want to deploy to.

sst.config.ts
const provider = new aws.Provider("MyProvider", { region: "us-west-2" });

And then pass that in to the resource.

sst.config.ts
new sst.aws.Function("MyFunction", { handler: "index.handler" }, { provider });

If no provider is passed in, the default provider will be used. And if no region is specified, the default region from your credentials will be used.

sst.config.ts
const east = new sst.aws.Function("MyEastFunction", {
url: true,
handler: "index.handler",
});
const provider = new aws.Provider("MyWestProvider", { region: "us-west-2" });
const west = new sst.aws.Function(
"MyWestFunction",
{
url: true,
handler: "index.handler",
},
{ provider }
);
return {
east: east.url,
west: west.url,
};

View the full example.


Prisma in Lambda

To use Prisma in a Lambda function you need:

  1. @prisma/client package
  2. The generated Prisma client from npx prisma generate

You don’t need a layer to deploy these because nodejs.install automatically uses the right binary for the target Lambda architecture.

sst.config.ts
{
nodejs: { install: ["@prisma/client"] }
}

However, this overwrites the default client in node_modules/.prisma/client that’s generated by Prisma. So we need to use a different directory.

prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../.prisma/client"
}

And then we need to copy the generated client to the function.

sst.config.ts
{
copyFiles: [{
from: ".prisma/client/"
}]
}

We also need to import this client in the function.

prisma.ts
import { PrismaClient } from "./.prisma/client";

Prisma in serverless environments

Prisma unfortunately is not great in serverless environments. For a couple of reasons:

  1. It doesn’t support Data API, so you need to manage the connection pool on your own.
  2. Without the Data API, your functions need to run inside a VPC.
  3. Due to the internal architecture of their client, it’s also has slower cold starts.

Instead we recommend using Drizzle. This example is here for reference for people that are already using Prisma.

sst.config.ts
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
const rds = new sst.aws.Postgres("MyPostgres", { vpc });
const api = new sst.aws.Function("MyApi", {
vpc,
url: true,
link: [rds],
handler: "index.handler",
copyFiles: [{
from: ".prisma/client/",
}],
nodejs: { install: ["@prisma/client"] },
});
return {
api: api.url,
};

View the full example.


Puppeteer in Lambda

To use Puppeteer in a Lambda function you need:

  1. puppeteer-core
  2. Chromium
    • In sst dev, we’ll use a locally installed Chromium version.
    • In sst deploy, we’ll use the @sparticuz/chromium package. It comes with a pre-built binary for Lambda.

Chromium version

Since Puppeteer has a preferred version of Chromium, we’ll need to check the version of Chrome that a given version of Puppeteer supports. Head over to the Puppeteer’s Chromium Support page and check which versions work together.

For example, Puppeteer v23.1.1 supports Chrome for Testing 127.0.6533.119. So, we’ll use the v127 of @sparticuz/chromium.

Terminal window
npm install puppeteer-core@23.1.1 @sparticuz/chromium@127.0.0

Install Chromium locally

To use this locally, you’ll need to install Chromium.

Terminal window
npx @puppeteer/browsers install chromium@latest --path /tmp/localChromium

Once installed you’ll see the location of the Chromium binary, /tmp/localChromium/chromium/mac_arm-1350406/chrome-mac/Chromium.app/Contents/MacOS/Chromium.

Update this in your Lambda function.

index.ts
// This is the path to the local Chromium binary
const YOUR_LOCAL_CHROMIUM_PATH = "/tmp/localChromium/chromium/mac_arm-1350406/chrome-mac/Chromium.app/Contents/MacOS/Chromium";

You’ll notice we are using the right binary with the SST_DEV environment variable.

index.ts
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: process.env.SST_DEV
? YOUR_LOCAL_CHROMIUM_PATH
: await chromium.executablePath(),
headless: chromium.headless,
});

Deploy

We don’t need a layer to deploy this because @sparticuz/chromium comes with a pre-built binary for Lambda.

We just need to set it in the nodejs.install.

sst.config.ts
{
nodejs: {
install: ["@sparticuz/chromium"]
}
}

And on deploy, SST will use the right binary.

We are giving our function more memory and a longer timeout since running Puppeteer can take a while.

sst.config.ts
const api = new sst.aws.Function("MyFunction", {
url: true,
memory: "2 GB",
timeout: "15 minutes",
handler: "index.handler",
nodejs: {
install: ["@sparticuz/chromium"],
},
});
return {
url: api.url,
};

View the full example.


Subscribe to queues

Create an SQS queue, subscribe to it, and publish to it from a function.

sst.config.ts
const queue = new sst.aws.Queue("MyQueue");
queue.subscribe("subscriber.handler");
const app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [queue],
url: true,
});
return {
app: app.url,
queue: queue.url,
};

View the full example.


Router and function URL

Creates a router that routes all requests to a function with a URL.

sst.config.ts
const api = new sst.aws.Function("MyApi", {
handler: "api.handler",
url: true,
});
const bucket = new sst.aws.Bucket("MyBucket", {
public: true,
});
const router = new sst.aws.Router("MyRouter", {
domain: "router.ion.dev.sst.dev",
routes: {
"/api/*": api.url,
"/*": $interpolate`https://${bucket.domain}`,
},
});
return {
router: router.url,
bucket: bucket.domain,
};

View the full example.


Sharp in Lambda

Uses the Sharp library to resize images. In this example, it resizes a logo.png local file to 100x100 pixels.

sst.config.ts
{
nodejs: { install: ["sharp"] }
}

We don’t need a layer to deploy this because sharp comes with a pre-built binary for Lambda. This is handled by nodejs.install.

In dev, this uses the sharp npm package locally.

package.json
{
"dependencies": {
"sharp": "^0.33.5"
}
}

On deploy, SST will use the right binary from the sharp package for the target Lambda architecture.

sst.config.ts
const func = new sst.aws.Function("MyFunction", {
url: true,
handler: "index.handler",
nodejs: { install: ["sharp"] },
copyFiles: [{ from: "logo.png" }],
});
return {
url: func.url,
};

View the full example.


Simple static site

Deploy a simple HTML file as a static site with S3 and CloudFront.

sst.config.ts
// Deploys the current directory as a static site
new sst.aws.StaticSite("MySite", {
path: "./dist",
});

View the full example.


Swift in Lambda

Deploys a simple Swift application to Lambda using the al2023 runtime.

Check out the README in the repo for more details.

sst.config.ts
const swift = new sst.aws.Function("Swift", {
runtime: "provided.al2023",
architecture: process.arch === "arm64" ? "arm64" : "x86_64",
bundle: build("app"),
handler: "bootstrap",
url: true,
});
const router = new sst.aws.Router("SwiftRouter", {
routes: {
"/*": swift.url,
},
domain: "swift.dev.sst.dev",
});
return {
url: router.url,
};

View the full example.


Subscribe to topics

Create an SNS topic, publish to it from a function, and subscribe to it with a function and a queue.

sst.config.ts
const queue = new sst.aws.Queue("MyQueue");
queue.subscribe("subscriber.handler");
const topic = new sst.aws.SnsTopic("MyTopic");
topic.subscribe("subscriber.handler", {});
topic.subscribeQueue(queue.arn);
const app = new sst.aws.Function("MyApp", {
handler: "publisher.handler",
link: [topic],
url: true,
});
return {
app: app.url,
topic: topic.name,
};

View the full example.


Store and search for vector data using the Vector component. Includes a seeder API that uses an LLM to generate embeddings for some movies and optionally their posters.

Once seeded, you can call the search API to query the vector database.

sst.config.ts
const OpenAiApiKey = new sst.Secret("OpenAiApiKey");
const vector = new sst.aws.Vector("MyVectorDB", {
dimension: 1536,
});
const seeder = new sst.aws.Function("Seeder", {
handler: "index.seeder",
link: [OpenAiApiKey, vector],
copyFiles: [
{ from: "iron-man.jpg", to: "iron-man.jpg" },
{
from: "black-widow.jpg",
to: "black-widow.jpg",
},
{
from: "spider-man.jpg",
to: "spider-man.jpg",
},
{ from: "thor.jpg", to: "thor.jpg" },
{
from: "captain-america.jpg",
to: "captain-america.jpg",
},
],
url: true,
});
const app = new sst.aws.Function("MyApp", {
handler: "index.app",
link: [OpenAiApiKey, vector],
url: true,
});
return { seeder: seeder.url, app: app.url };

View the full example.


React SPA with Vite

Deploy a React single-page app (SPA) with Vite to S3 and CloudFront.

sst.config.ts
new sst.aws.StaticSite("Web", {
build: {
command: "pnpm run build",
output: "dist",
},
});

View the full example.


Cloudflare KV

This example creates a Cloudflare KV namespace and links it to a worker. Now you can use the SDK to interact with the KV namespace in your worker.

sst.config.ts
const storage = new sst.cloudflare.Kv("MyStorage");
const worker = new sst.cloudflare.Worker("Worker", {
url: true,
link: [storage],
handler: "index.ts",
});
return {
url: worker.url,
};

View the full example.


You might have multiple secrets that need to be used across your app. It can be tedious to create a new secret and link it to each function or resource.

A common pattern to addresses this is to create an object with all your secrets and then link them all at once. Now when you have a new secret, you can add it to the object and it will be automatically available to all your resources.

sst.config.ts
// Manage all secrets together
const secrets = {
secret1: new sst.Secret("Secret1", "some-secret-value-1"),
secret2: new sst.Secret("Secret2", "some-secret-value-2"),
};
const allSecrets = Object.values(secrets);
const bucket = new sst.aws.Bucket("MyBucket");
const api = new sst.aws.Function("MyApi", {
link: [bucket, ...allSecrets],
handler: "index.handler",
url: true,
});
return {
url: api.url,
};

View the full example.


Default function props

Set default props for all the functions in your app using the global $transform.

sst.config.ts
$transform(sst.aws.Function, (args) => {
args.runtime = "nodejs14.x";
args.environment = {
FOO: "BAR",
};
});
new sst.aws.Function("MyFunction", {
handler: "index.ts",
});

View the full example.


Vercel domains

Creates a router that uses domains purchased through and hosted in your Vercel account. Ensure the VERCEL_API_TOKEN and VERCEL_TEAM_ID environment variables are set.

sst.config.ts
const router = new sst.aws.Router("MyRouter", {
domain: {
name: "ion.sst.moe",
dns: sst.vercel.dns({ domain: "sst.moe" }),
},
routes: {
"/*": "https://sst.dev",
},
});
return {
router: router.url,
};

View the full example.