Examples
A collection of example apps for reference.
Below is 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.
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.
AWS Astro container with Redis
Creates a hit counter app with Astro and Redis.
This deploys Astro as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:4321
you’ll see a counter update as you refresh the page.
Finally, you can deploy it by adding the Dockerfile
that’s included in this example and
running npx sst deploy --stage production
.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "4321/http" }], }, dev: { command: "npm run dev", },});
View the full example.
AWS Astro streaming
Follows the Astro Streaming guide to create an app that streams HTML.
The responseMode
in the astro-sst
adapter
is set to enable streaming.
adapter: aws({ responseMode: "stream"})
Now any components that return promises will be streamed.
---import type { Character } from "./character";
const friends: Character[] = await new Promise((resolve) => setTimeout(() => { setTimeout(() => { resolve( [ { name: "Patrick Star", image: "patrick.png" }, { name: "Sandy Cheeks", image: "sandy.png" }, { name: "Squidward Tentacles", image: "squidward.png" }, { name: "Mr. Krabs", image: "mr-krabs.png" }, ] ); }, 3000);}));---<div class="grid"> {friends.map((friend) => ( <div class="card"> <img class="img" src={friend.image} alt={friend.name} /> <p>{friend.name}</p> </div> ))}</div>
You should see the friends section load after a 3 second delay.
Safari uses a different heuristic to determine when to stream data. You need to render enough initial HTML to trigger streaming. This is typically only a problem for demo apps.
There’s nothing to configure for streaming in the Astro
component.
new sst.aws.Astro("MyWeb");
View the full example.
AWS Aurora local
In this example, we connect to a locally running Postgres instance for dev. While on deploy, we use RDS Aurora.
We use the docker run
CLI
to start a local container with Postgres. You don’t have to use Docker, you can use
Postgres.app or any other way to run Postgres locally.
docker run \ --rm \ -p 5432:5432 \ -v $(pwd)/.sst/storage/postgres:/var/lib/postgresql/data \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=password \ -e POSTGRES_DB=local \ postgres:16.4
The data is saved to the .sst/storage
directory. So if you restart the dev server, the
data will still be there.
We then configure the dev
property of the Aurora
component with the settings for the
local Postgres instance.
dev: { username: "postgres", password: "password", database: "local", port: 5432,}
By providing the dev
prop for Postgres, SST will use the local Postgres instance and
not deploy a new RDS database when running sst dev
.
It also allows us to access the database through a Resource link
without having to
conditionally check if we are running locally.
const pool = new Pool({ host: Resource.MyPostgres.host, port: Resource.MyPostgres.port, user: Resource.MyPostgres.username, password: Resource.MyPostgres.password, database: Resource.MyPostgres.database,});
The above will work in both sst dev
and sst deploy
.
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });
const database = new sst.aws.Aurora("MyPostgres", { engine: "postgres", dev: { username: "postgres", password: "password", database: "local", host: "localhost", port: 5432, }, vpc,});
new sst.aws.Function("MyFunction", { vpc, url: true, link: [database], handler: "index.handler",});
View the full example.
AWS Aurora MySQL
In this example, we deploy a Aurora MySQL database.
const mysql = new sst.aws.Aurora("MyDatabase", { engine: "mysql", vpc,});
And link it to a Lambda function.
new sst.aws.Function("MyApp", { handler: "index.handler", link: [mysql], url: true, vpc,});
Now in the function we can access the database.
const connection = await mysql.createConnection({ database: Resource.MyDatabase.database, host: Resource.MyDatabase.host, port: Resource.MyDatabase.port, user: Resource.MyDatabase.username, password: Resource.MyDatabase.password,});
We also enable the bastion
option for the VPC. This allows us to connect to the database
from our local machine with the sst tunnel
CLI.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
Now you can run npx sst dev
and you can connect to the database from your local machine.
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2", bastion: true,});const mysql = new sst.aws.Aurora("MyDatabase", { engine: "mysql", vpc,});new sst.aws.Function("MyApp", { handler: "index.handler", link: [mysql], url: true, vpc,});
return { host: mysql.host, port: mysql.port, username: mysql.username, password: mysql.password, database: mysql.database,};
View the full example.
AWS Aurora Postgres
In this example, we deploy a Aurora Postgres database.
const postgres = new sst.aws.Aurora("MyDatabase", { engine: "postgres", vpc,});
And link it to a Lambda function.
new sst.aws.Function("MyApp", { handler: "index.handler", link: [postgres], url: true, vpc,});
In the function we use the postgres
package.
import postgres from "postgres";import { Resource } from "sst";
const sql = postgres({ username: Resource.MyDatabase.username, password: Resource.MyDatabase.password, database: Resource.MyDatabase.database, host: Resource.MyDatabase.host, port: Resource.MyDatabase.port,});
We also enable the bastion
option for the VPC. This allows us to connect to the database
from our local machine with the sst tunnel
CLI.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
Now you can run npx sst dev
and you can connect to the database from your local machine.
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2", bastion: true,});const postgres = new sst.aws.Aurora("MyDatabase", { engine: "postgres", vpc,});new sst.aws.Function("MyApp", { handler: "index.handler", link: [postgres], url: true, vpc,});
return { host: postgres.host, port: postgres.port, username: postgres.username, password: postgres.password, database: postgres.database,};
View the full example.
AWS OpenAuth React SPA
This is a full-stack monorepo app shows the OpenAuth flow for a single-page app and an authenticated API. It has:
-
React SPA built with Vite and the
StaticSite
component in thepackages/web
directory.infra/web.ts export const web = new sst.aws.StaticSite("MyWeb", {path: "packages/web",build: {output: "dist",command: "npm run build",},environment: {VITE_API_URL: api.url,VITE_AUTH_URL: auth.url,},}); -
API with Hono and the
Function
component inpackages/functions/src/api.ts
.infra/api.ts export const api = new sst.aws.Function("MyApi", {url: true,link: [auth],handler: "packages/functions/src/api.handler",}); -
OpenAuth with the
Auth
component inpackages/functions/src/auth.ts
.infra/auth.ts export const auth = new sst.aws.Auth("MyAuth", {issuer: "packages/functions/src/auth.handler",});
The React frontend uses a AuthContext
provider to manage the auth flow.
<AuthContext.Provider value={{ login, logout, userId, loaded, loggedIn, getToken, }}> {children}</AuthContext.Provider>
Now in App.tsx
, we can use the useAuth
hook.
const auth = useAuth();
return !auth.loaded ? ( <div>Loading...</div>) : ( <div> {auth.loggedIn ? ( <div> <p> <span>Logged in</span> {auth.userId && <span> as {auth.userId}</span>} </p> </div> ) : ( <button onClick={auth.login}>Login with OAuth</button> )} </div>);
Once authenticated, we can call our authenticated API by passing in the access token.
await fetch(`${import.meta.env.VITE_API_URL}me`, { headers: { Authorization: `Bearer ${await auth.getToken()}`, },});
The API uses the OpenAuth client to verify the token.
const authHeader = c.req.header("Authorization");const token = authHeader.split(" ")[1];const verified = await client.verify(subjects, token);
The sst.config.ts
dynamically imports all the infra/
files.
await import("./infra/auth");await import("./infra/api");await import("./infra/web");
View the full example.
Bucket policy
Create an S3 bucket and transform its bucket policy.
const bucket = new sst.aws.Bucket("MyBucket", { transform: { policy: (args) => { // use sst.aws.iamEdit helper function to manipulate IAM policy // containing Output values from components args.policy = sst.aws.iamEdit(args.policy, (policy) => { policy.Statement.push({ Effect: "Allow", Principal: { Service: "ses.amazonaws.com" }, Action: "s3:PutObject", Resource: $interpolate`arn:aws:s3:::${args.bucket}/*`, }); }); }, },});
return { bucket: bucket.name,};
View the full example.
Bucket queue notifications
Create an S3 bucket and subscribe to its events with an SQS queue.
const queue = new sst.aws.Queue("MyQueue");queue.subscribe("subscriber.handler");
const bucket = new sst.aws.Bucket("MyBucket");bucket.notify({ notifications: [ { name: "MySubscriber", queue, 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.
const bucket = new sst.aws.Bucket("MyBucket");bucket.notify({ notifications: [ { name: "MySubscriber", function: "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.
const topic = new sst.aws.SnsTopic("MyTopic");topic.subscribe("MySubscriber", "subscriber.handler");
const bucket = new sst.aws.Bucket("MyBucket");bucket.notify({ notifications: [ { name: "MySubscriber", topic, events: ["s3:ObjectCreated:*"], }, ],});
return { bucket: bucket.name, topic: topic.name,};
View the full example.
AWS Bun Elysia container
Deploys a Bun Elysia API to AWS.
You can get started by running.
bun create elysia aws-bun-elysiacd aws-bun-elysiabunx sst init
Now you can add a service.
new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "bun dev", },});
Start your app locally.
bun sst dev
This example lets you upload a file to S3 and then download it.
curl -F file=@elysia.png http://localhost:3000/curl http://localhost:3000/latest
Finally, you can deploy it using bun sst deploy --stage production
.
const bucket = new sst.aws.Bucket("MyBucket");const vpc = new sst.aws.Vpc("MyVpc");
const cluster = new sst.aws.Cluster("MyCluster", { vpc });new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "bun dev", }, link: [bucket],});
View the full example.
AWS Bun Redis
Creates a hit counter app with Bun and Redis.
This deploys Bun as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "bun dev", }, link: [redis],});
We also have a couple of scripts. A dev
script with a watcher and a build
script
that used when we deploy to production.
{ "scripts": { "dev": "bun run --watch index.ts", "build": "bun build --target bun index.ts" },}
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo bun sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
bun sst dev
Now if you go to http://localhost:3000
you’ll see a counter update as you refresh the page.
Finally, you can deploy it using bun sst deploy --stage production
using a Dockerfile
that’s included in the example.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "bun dev", },});
View the full example.
AWS Cluster private service
Adds a private load balancer to a service by setting the loadBalancer.public
prop to
false
.
This allows you to create internal services that can only be accessed inside a VPC.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });
const cluster = new sst.aws.Cluster("MyCluster", { vpc });new sst.aws.Service("MyService", { cluster, loadBalancer: { public: false, ports: [{ listen: "80/http" }], },});
View the full example.
AWS Cluster Spot capacity
This example, shows how to use the Fargate Spot capacity provider for your services.
We have it set to use only Fargate Spot instances for all non-production stages. Learn more
about the capacity
prop.
const vpc = new sst.aws.Vpc("MyVpc");
const cluster = new sst.aws.Cluster("MyCluster", { vpc });new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http" }], }, capacity: $app.stage === "production" ? undefined : "spot",});
View the full example.
AWS Cluster with API Gateway
Expose a service through API Gateway HTTP API using a VPC link.
This is an alternative to using a load balancer. Since API Gateway is pay per request, it works out a lot cheaper for services that don’t get a lot of traffic.
You need to specify which port in your service will be exposed through API Gateway.
const service = new sst.aws.Service("MyService", { cluster, serviceRegistry: { port: 80, },});
Your API Gateway HTTP API also needs to be in the same VPC as the service.
const vpc = new sst.aws.Vpc("MyVpc");const cluster = new sst.aws.Cluster("MyCluster", { vpc });const service = new sst.aws.Service("MyService", { cluster, serviceRegistry: { port: 80, },});
const api = new sst.aws.ApiGatewayV2("MyApi", { vpc });api.routePrivate("$default", service.nodes.cloudmapService.arn);
View the full example.
Subscribe to queues
Create an SQS queue, subscribe to it, and publish to it from a function.
// create dead letter queueconst dlq = new sst.aws.Queue("DeadLetterQueue");dlq.subscribe("subscriber.dlq");
// create main queueconst 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.
AWS Deno Redis
Creates a hit counter app with Deno and Redis.
This deploys Deno as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "8000/http" }], }, dev: { command: "deno task dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
sst dev
Now if you go to http://localhost:8000
you’ll see a counter update as you refresh the page.
Finally, you can deploy it using sst deploy --stage production
using a Dockerfile
that’s included in the example.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "8000/http" }], }, dev: { command: "deno task dev", },});
View the full example.
DynamoDB streams
Create a DynamoDB table, enable streams, and subscribe to it with a function.
const table = new sst.aws.Dynamo("MyTable", { fields: { id: "string", }, primaryIndex: { hashKey: "id" }, stream: "new-and-old-images",});table.subscribe("MySubscriber", "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.
EC2 with Pulumi
Use raw Pulumi resources to create an EC2 instance.
// Notice you don't need to import pulumi, it is already part of sst.const securityGroup = new aws.ec2.SecurityGroup("web-secgrp", { ingress: [ { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"], }, ],});
// Find the latest Ubuntu AMIconst ami = aws.ec2.getAmi({ filters: [ { name: "name", values: ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"], }, ], mostRecent: true, owners: ["099720109477"], // Canonical});
// User data to set up a simple web serverconst userData = `#!/bin/bashho "Hello, World!" > index.htmlhup python3 -m http.server 80 &`;
// Create an EC2 instanceconst server = new aws.ec2.Instance("web-server", { instanceType: "t2.micro", ami: ami.then((ami) => ami.id), userData: userData, vpcSecurityGroupIds: [securityGroup.id], associatePublicIpAddress: true,});
return { app: server.publicIp,};
View the full example.
AWS EFS with SQLite
Mount an EFS file system to a function and write to a SQLite database.
const db = sqlite3("/mnt/efs/mydb.sqlite");
The file system is mounted to /mnt/efs
in the function.
This example is for demonstration purposes only. It’s not recommended to use EFS for databases in production.
// NAT Gateways are required for Lambda functionsconst vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
// Create an EFS file system to store the SQLite databaseconst efs = new sst.aws.Efs("MyEfs", { vpc });
// Create a Lambda function that queries the databasenew sst.aws.Function("MyFunction", { vpc, url: true, volume: { efs, path: "/mnt/efs", }, handler: "index.handler", nodejs: { install: ["better-sqlite3"], },});
View the full example.
AWS EFS with SurrealDB
We use the SurrealDB docker image to run a server in a container and use EFS as the file system.
const server = new sst.aws.Service("MyService", { cluster, architecture: "arm64", image: "surrealdb/surrealdb:v2.0.2", // ... volumes: [ { efs, path: "/data" }, ],});
We then connect to the server from a Lambda function.
const endpoint = `http://${Resource.MyConfig.host}:${Resource.MyConfig.port}`;
const db = new Surreal();await db.connect(endpoint);
This uses the SurrealDB client to connect to the server.
This example is for demonstration purposes only. It’s not recommended to use EFS for databases in production.
const { RandomPassword } = await import("@pulumi/random");
// SurrealDB Credentialsconst PORT = 8080;const NAMESPACE = "test";const DATABASE = "test";const USERNAME = "root";const PASSWORD = new RandomPassword("Password", { length: 32,}).result;
// NAT Gateways are required for Lambda functionsconst vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
// Store SurrealDB data in EFSconst efs = new sst.aws.Efs("MyEfs", { vpc });
// Run SurrealDB server in a containerconst cluster = new sst.aws.Cluster("MyCluster", { vpc });const server = new sst.aws.Service("MyService", { cluster, architecture: "arm64", image: "surrealdb/surrealdb:v2.0.2", command: [ "start", "--bind", $interpolate`0.0.0.0:${PORT}`, "--log", "info", "--user", USERNAME, "--pass", PASSWORD, "surrealkv://data/data.skv", "--allow-scripting", ], volumes: [{ efs, path: "/data" }],});
// Lambda client to connect to SurrealDBconst config = new sst.Linkable("MyConfig", { properties: { username: USERNAME, password: PASSWORD, namespace: NAMESPACE, database: DATABASE, port: PORT, host: server.service, },});
new sst.aws.Function("MyApp", { handler: "index.handler", link: [config], url: true, vpc,});
View the full example.
AWS EFS
Mount an EFS file system to a function and a container.
This allows both your function and the container to access the same file system. Here they both update a counter that’s stored in the file system.
await writeFile("/mnt/efs/counter", newValue.toString());
The file system is mounted to /mnt/efs
in both the function and the container.
// NAT Gateways are required for Lambda functionsconst vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
// Create an EFS file system to store a counterconst efs = new sst.aws.Efs("MyEfs", { vpc });
// Create a Lambda function that increments the counternew sst.aws.Function("MyFunction", { handler: "lambda.handler", url: true, vpc, volume: { efs, path: "/mnt/efs", },});
// Create a service that increments the same counterconst cluster = new sst.aws.Cluster("MyCluster", { vpc });new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http" }], }, volumes: [ { efs, path: "/mnt/efs", }, ],});
View the full example.
AWS Express Redis
Creates a hit counter app with Express and Redis.
This deploys Express as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http" }], }, dev: { command: "node --watch index.mjs", }, link: [redis],});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:80
you’ll see a counter update as you refresh the page.
Finally, you can deploy it using npx sst deploy --stage production
using a Dockerfile
that’s included in the example.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http" }], }, dev: { command: "node --watch index.mjs", },});
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.
import ffmpeg from "ffmpeg-static";
We can use this to spawn a child process and run FFmpeg.
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
.
{ nodejs: { install: ["ffmpeg-static"] }}
All this is handled by nodejs.install
.
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.
AWS ApiGatewayV2 Go
Uses aws-lambda-go-api-proxy to allow you to run a Go API with API Gateway V2.
So you write your Go function as you normally would and then use the package to handle the API Gateway V2 event.
import ( "github.com/aws/aws-lambda-go/lambda" "github.com/awslabs/aws-lambda-go-api-proxy/httpadapter")
func router() *http.ServeMux { mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message": "hello world"}`)) })
return mux}
func main() { lambda.Start(httpadapter.NewV2(router()).ProxyWithContext)}
const api = new sst.aws.ApiGatewayV2("GoApi");
api.route("$default", { handler: "src/", runtime: "go",});
View the full example.
AWS Lambda Go S3 Presigned
Generates a presigned URL for the linked S3 bucket in a Go Lambda function.
Configure the S3 Client and the PresignedClient.
cfg, err := config.LoadDefaultConfig(context.TODO())if err != nil { panic(err)}
client := s3.NewFromConfig(cfg)presignedClient := s3.NewPresignClient(client)
Generate the presigned URL.
bucketName, err := resource.Get("Bucket", "name")if err != nil { panic(err)}url, err := presignedClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String(bucket.(string)), Key: aws.String(key),})
const bucket = new sst.aws.Bucket("Bucket");
const api = new sst.aws.ApiGatewayV2("Api");
api.route("GET /upload-url", { handler: "src/", runtime: "go", link: [bucket],});
View the full example.
AWS Lambda Go DynamoDB
An example on how to use a Go runtime Lambda with DynamoDB.
You configure the DynamoDB client.
import ( "github.com/sst/sst/v3/sdk/golang/resource")
func main() { cfg, err := config.LoadDefaultConfig(context.Background()) if err != nil { panic(err) } client := dynamodb.NewFromConfig(cfg)
tableName, err := resource.Get("Table", "name") if err != nil { panic(err) }}
And make a request to DynamoDB.
_, err = r.client.PutItem(ctx, &dynamodb.PutItemInput{ TableName: tableName.(string), Item: item,})
const table = new sst.aws.Dynamo("Table", { fields: { PK: "string", SK: "string", }, primaryIndex: { hashKey: "PK", rangeKey: "SK" },});
new sst.aws.Function("GoFunction", { url: true, runtime: "go", handler: "./src", link: [table],});
View the full example.
AWS Hono container with Redis
Creates a hit counter app with Hono and Redis.
This deploys Hono API as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:3000
you’ll see a counter update as you refresh the page.
Finally, you can deploy it by:
-
Using the
Dockerfile
that’s included in this example. -
This compiles our TypeScript file, so we’ll need add the following to the
tsconfig.json
.tsconfig.json {"compilerOptions": {// ..."outDir": "./dist"},"exclude": ["node_modules"]} -
Install TypeScript.
Terminal window npm install typescript --save-dev -
And add a
build
script to ourpackage.json
.package.json "scripts": {// ..."build": "tsc"}
And finally, running npx sst deploy --stage production
.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
View the full example.
AWS Hono streaming
An example on how to enable streaming for Lambda functions using Hono.
{ streaming: true}
While sst dev
doesn’t support streaming, we can conditionally enable it on deploy.
export const handler = process.env.SST_LIVE ? handle(app) : streamHandle(app);
This will return the standard handler for sst dev
.
To test this in your terminal, use the curl
command with the --no-buffer
option.
curl --no-buffer https://u3dyblk457ghskwbmzrbylpxoi0ayrbb.lambda-url.us-east-1.on.aws
Here we are using a Function URL directly because API Gateway doesn’t support streaming.
const hono = new sst.aws.Function("Hono", { url: true, streaming: true, timeout: "15 minutes", handler: "index.handler",});return { api: hono.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.
// Create a permission boundaryconst 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 roleconst 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.
return { region: aws.getRegionOutput().name, account: aws.getCallerIdentityOutput({}).accountId,};
View the full example.
AWS JSX Email
Uses JSX Email and the Email
component to design and send emails.
To test this example, change the sst.config.ts
to use your own email address.
sender: "email@example.com"
Then run.
npm installnpx sst dev
You’ll get an email from AWS asking you to confirm your email address. Click the link to verify it.
Next, go to the URL in the sst dev
CLI output. You should now receive an email rendered
using JSX Email.
import { Template } from "./templates/email";
await render(Template({ email: "spongebob@example.com", name: "Spongebob Squarepants"}))
Once you are ready to go to production, you can:
- Request production access for SES
- And use your domain to send emails
const email = new sst.aws.Email("MyEmail", { sender: "email@example.com",});const api = new sst.aws.Function("MyApi", { handler: "index.handler", link: [email], url: true,});
return { api: api.url,};
View the full example.
Kinesis streams
Create a Kinesis stream, and subscribe to it with a function.
const stream = new sst.aws.KinesisStream("MyStream");
// Create a function subscribing to all eventsstream.subscribe("AllSub", "subscriber.all");
// Create a function subscribing to events of `bar` typestream.subscribe("FilteredSub", "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 Lambda Go
This example shows how to use the go
runtime in your Lambda
functions.
Our Go function is in the src
directory and we point to it in our function.
new sst.aws.Function("MyFunction", { url: true, runtime: "go", link: [bucket], handler: "./src",});
We are also linking it to an S3 bucket. We can reference the bucket in our function.
func handler() (string, error) { bucket, err := resource.Get("MyBucket", "name") if err != nil { return "", err } return bucket.(string), nil}
The resource.Get
function is from the SST Go SDK.
import ( "github.com/sst/sst/v3/sdk/golang/resource")
The sst dev
CLI also supports running your Go function Live.
const bucket = new sst.aws.Bucket("MyBucket");
new sst.aws.Function("MyFunction", { url: true, runtime: "go", link: [bucket], handler: "./src",});
View the full example.
AWS Lambda retry with queues
An example on how to retry Lambda invocations using SQS queues.
Create a SQS retry queue which will be set as the destination for the Lambda function.
const retryQueue = new sst.aws.Queue("retryQueue");
const bus = new sst.aws.Bus("bus");
const busSubscriber = bus.subscribe("busSubscriber", { handler: "src/bus-subscriber.handler", environment: { RETRIES: "2", // set the number of retries }, link: [retryQueue], // so the function can send messages to the retry queue});
new aws.lambda.FunctionEventInvokeConfig("eventConfig", { functionName: $resolve([busSubscriber.nodes.function.name]).apply( ([name]) => name, ), maximumRetryAttempts: 2, // default is 2, must be between 0 and 2 destinationConfig: { onFailure: { destination: retryQueue.arn, }, },});
Create a bus subscriber which will publish messages to the bus. Include a DLQ for messages that continue to fail.
const dlq = new sst.aws.Queue("dlq");
retryQueue.subscribe({ handler: "src/retry.handler", link: [busSubscriber.nodes.function, retryQueue, dlq], timeout: "30 seconds", environment: { RETRIER_QUEUE_URL: retryQueue.url, }, permissions: [ { actions: ["lambda:GetFunction", "lambda:InvokeFunction"], resources: [ $interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${ aws.getCallerIdentityOutput().accountId }:function:*`, ], }, ], transform: { function: { deadLetterConfig: { targetArn: dlq.arn, }, }, },});
The Retry function will read mesaages and send back to the queue to be retried with a backoff.
export const handler: SQSHandler = async (evt) => { for (const record of evt.Records) { const parsed = JSON.parse(record.body); console.log("body", parsed); const functionName = parsed.requestContext.functionArn .replace(":$LATEST", "") .split(":") .pop(); if (parsed.responsePayload) { const attempt = (parsed.requestPayload.attempts || 0) + 1;
const info = await lambda.send( new GetFunctionCommand({ FunctionName: functionName, }), ); const max = Number.parseInt( info.Configuration?.Environment?.Variables?.RETRIES || "", ) || 0; console.log("max retries", max); if (attempt > max) { console.log(`giving up after ${attempt} retries`); // send to dlq await sqs.send( new SendMessageCommand({ QueueUrl: Resource.dlq.url, MessageBody: JSON.stringify({ requestPayload: parsed.requestPayload, requestContext: parsed.requestContext, responsePayload: parsed.responsePayload, }), }), ); return; } const seconds = Math.min(Math.pow(2, attempt), 900); console.log( "delaying retry by ", seconds, "seconds for attempt", attempt, ); parsed.requestPayload.attempts = attempt; await sqs.send( new SendMessageCommand({ QueueUrl: Resource.retryQueue.url, DelaySeconds: seconds, MessageBody: JSON.stringify({ requestPayload: parsed.requestPayload, requestContext: parsed.requestContext, }), }), ); }
if (!parsed.responsePayload) { console.log("triggering function"); try { await lambda.send( new InvokeCommand({ InvocationType: "Event", Payload: Buffer.from(JSON.stringify(parsed.requestPayload)), FunctionName: functionName, }), ); } catch (e) { if (e instanceof ResourceNotFoundException) { return; } throw e; } } }};
const dlq = new sst.aws.Queue("dlq");
const retryQueue = new sst.aws.Queue("retryQueue");
const bus = new sst.aws.Bus("bus");
const busSubscriber = bus.subscribe("busSubscriber", { handler: "src/bus-subscriber.handler", environment: { RETRIES: "2", }, link: [retryQueue], // so the function can send messages to the queue});
const publisher = new sst.aws.Function("publisher", { handler: "src/publisher.handler", link: [bus], url: true,});
new aws.lambda.FunctionEventInvokeConfig("eventConfig", { functionName: $resolve([busSubscriber.nodes.function.name]).apply( ([name]) => name, ), maximumRetryAttempts: 1, destinationConfig: { onFailure: { destination: retryQueue.arn, }, },});
retryQueue.subscribe({ handler: "src/retry.handler", link: [busSubscriber.nodes.function, retryQueue, dlq], timeout: "30 seconds", environment: { RETRIER_QUEUE_URL: retryQueue.url, }, permissions: [ { actions: ["lambda:GetFunction", "lambda:InvokeFunction"], resources: [ $interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${ aws.getCallerIdentityOutput().accountId }:function:*`, ], }, ], transform: { function: { deadLetterConfig: { targetArn: dlq.arn, }, }, },});
return { publisher: publisher.url, dlq: dlq.url, retryQueue: retryQueue.url,};
View the full example.
AWS Lambda streaming
An example on how to enable streaming for Lambda functions.
{ streaming: true}
While sst dev
doesn’t support streaming, you can use the
lambda-stream
package to test locally.
npm install lambda-stream
Then, you can use the streamifyResponse
function to wrap your handler:
import { APIGatewayProxyEventV2 } from "aws-lambda";import { streamifyResponse, ResponseStream } from "lambda-stream";
export const handler = streamifyResponse(myHandler);
async function myHandler( _event: APIGatewayProxyEventV2, responseStream: ResponseStream): Promise<void> { return new Promise((resolve, _reject) => { responseStream.setContentType('text/plain') responseStream.write('Hello') setTimeout(() => { responseStream.write(' World') responseStream.end() resolve() }, 3000) })}
When deployed, this will use the awslambda.streamifyResponse
.
To test this in your terminal, use the curl
command with the --no-buffer
option.
curl --no-buffer https://u3dyblk457ghskwbmzrbylpxoi0ayrbb.lambda-url.us-east-1.on.aws
Here we are using a Function URL directly because API Gateway doesn’t support streaming.
const fn = new sst.aws.Function("MyFunction", { url: true, streaming: true, timeout: "15 minutes", handler: "index.handler",});
return { url: fn.url,};
View the full example.
AWS Lambda in a VPC
You can use SST to locally work on Lambda functions that are in a VPC. To do so, you’ll
need to enable bastion
and nat
on the Vpc
component.
new sst.aws.Vpc("MyVpc", { bastion: true, nat: "managed" });
The NAT gateway is necessary to allow your Lambda function to connect to the internet. While, the bastion host is necessary for your local machine to be able to tunnel to the VPC.
You’ll need to install the tunnel, if you haven’t done this before.
sudo sst tunnel install
This needs sudo to create the network interface on your machine. You’ll only need to do this once.
Now you can run sst dev
, your function can access resources in the VPC. For example, here
we are connecting to a Redis cluster.
const redis = new Cluster( [{ host: Resource.MyRedis.host, port: Resource.MyRedis.port }], { dnsLookup: (address, callback) => callback(null, address), redisOptions: { tls: {}, username: Resource.MyRedis.username, password: Resource.MyRedis.password, }, });
The Redis cluster is in the same VPC as the function.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true, nat: "managed" });const redis = new sst.aws.Redis("MyRedis", { vpc });const api = new sst.aws.Function("MyFunction", { vpc, url: true, link: [redis], handler: "index.handler"});
return { url: api.url,};
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.
const provider = new aws.Provider("MyProvider", { region: "us-west-2" });
And then pass that in to the resource.
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.
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.
AWS NestJS with Redis
Creates a hit counter app with NestJS and Redis.
Also make sure you have Node 22.12. Or set the --experimental-require-module
flag.
This’ll allow NestJS to import the SST SDK.
This deploys NestJS as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run start:dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:3000
you’ll see a counter update as you refresh the page.
Finally, you can deploy it using npx sst deploy --stage production
using a Dockerfile
that’s included in the example.
const vpc = new sst.aws.Vpc('MyVpc', { bastion: true });const redis = new sst.aws.Redis('MyRedis', { vpc });const cluster = new sst.aws.Cluster('MyCluster', { vpc });
new sst.aws.Service('MyService', { cluster, link: [redis], loadBalancer: { ports: [{ listen: '80/http', forward: '3000/http' }], }, dev: { command: 'npm run start:dev', },});
View the full example.
AWS Next.js add behavior
Here’s how to add additional routes or cache behaviors to the CDN of a Next.js app deployed with OpenNext to AWS.
Specify the path pattern that you want to forward to your new origin. For example, to forward
all requests to the /blog
path to a different origin.
pathPattern: "/blog/*"
And then specify the domain of the new origin.
domainName: "blog.example.com"
We use this to transform
our site’s CDN and add the additional behaviors.
const blogOrigin = { // The domain of the new origin domainName: "blog.example.com", originId: "blogCustomOrigin", customOriginConfig: { httpPort: 80, httpsPort: 443, originSslProtocols: ["TLSv1.2"], // If HTTPS is supported originProtocolPolicy: "https-only", },};
const cacheBehavior = { // The path to forward to the new origin pathPattern: "/blog/*", targetOriginId: blogOrigin.originId, viewerProtocolPolicy: "redirect-to-https", allowedMethods: ["GET", "HEAD", "OPTIONS"], cachedMethods: ["GET", "HEAD"], forwardedValues: { queryString: true, cookies: { forward: "all", }, },};
new sst.aws.Nextjs("MyWeb", { transform: { cdn: (options: sst.aws.CdnArgs) => { options.origins = $resolve(options.origins).apply(val => [...val, blogOrigin]);
options.orderedCacheBehaviors = $resolve( options.orderedCacheBehaviors || [] ).apply(val => [...val, cacheBehavior]); }, },});
View the full example.
AWS Next.js basic auth
Deploys a simple Next.js app and adds basic auth to it.
This is useful for dev environments where you want to share your app your team but ensure that it’s not publicly accessible.
This works by injecting some code into a CloudFront function that checks the basic auth
header and matches it against the USERNAME
and PASSWORD
secrets.
{ injection: $interpolate` if ( !event.request.headers.authorization || event.request.headers.authorization.value !== "Basic ${basicAuth}" ) { return { statusCode: 401, headers: { "www-authenticate": { value: "Basic" } } }; }`,}
To deploy this, you need to first set the USERNAME
and PASSWORD
secrets.
sst secret set USERNAME my-usernamesst secret set PASSWORD my-password
If you are deploying this to preview environments, you might want to set the secrets using
the --fallback
flag.
const username = new sst.Secret("USERNAME");const password = new sst.Secret("PASSWORD");const basicAuth = $resolve([username.value, password.value]).apply( ([username, password]) => Buffer.from(`${username}:${password}`).toString("base64"));
new sst.aws.Nextjs("MyWeb", { server: { // Don't password protect prod edge: $app.stage !== "production" ? { viewerRequest: { injection: $interpolate` if ( !event.request.headers.authorization || event.request.headers.authorization.value !== "Basic ${basicAuth}" ) { return { statusCode: 401, headers: { "www-authenticate": { value: "Basic" } } }; }`, }, } : undefined, },});
View the full example.
AWS Next.js container with Redis
Creates a hit counter app with Next.js and Redis.
This deploys Next.js as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:3000
you’ll see a counter update as you refresh the page.
Finally, you can deploy it by:
- Setting
output: "standalone"
in yournext.config.mjs
file. - Adding a
Dockerfile
that’s included in this example. - Running
npx sst deploy --stage production
.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
View the full example.
AWS Next.js streaming
An example of how to use streaming Next.js RSC. Uses Suspense
to stream an async component.
<Suspense fallback={<div>Loading...</div>}> <Friends /></Suspense>
For this demo we also need to make sure the route is not statically built.
export const dynamic = "force-dynamic";
This is deployed with OpenNext, which needs a config to enable streaming.
export default { default: { override: { wrapper: "aws-lambda-streaming" } }};
You should see the friends section load after a 3 second delay.
Safari uses a different heuristic to determine when to stream data. You need to render enough initial HTML to trigger streaming. This is typically only a problem for demo apps.
new sst.aws.Nextjs("MyWeb");
View the full example.
AWS Postgres local
In this example, we connect to a locally running Postgres instance for dev. While on deploy, we use RDS.
We use the docker run
CLI
to start a local container with Postgres. You don’t have to use Docker, you can use
Postgres.app or any other way to run Postgres locally.
docker run \ --rm \ -p 5432:5432 \ -v $(pwd)/.sst/storage/postgres:/var/lib/postgresql/data \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=password \ -e POSTGRES_DB=local \ postgres:16.4
The data is saved to the .sst/storage
directory. So if you restart the dev server, the
data will still be there.
We then configure the dev
property of the Postgres
component with the settings for the
local Postgres instance.
dev: { username: "postgres", password: "password", database: "local", port: 5432,}
By providing the dev
prop for Postgres, SST will use the local Postgres instance and
not deploy a new RDS database when running sst dev
.
It also allows us to access the database through a Resource link
without having to
conditionally check if we are running locally.
const pool = new Pool({ host: Resource.MyPostgres.host, port: Resource.MyPostgres.port, user: Resource.MyPostgres.username, password: Resource.MyPostgres.password, database: Resource.MyPostgres.database,});
The above will work in both sst dev
and sst deploy
.
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });
const rds = new sst.aws.Postgres("MyPostgres", { dev: { username: "postgres", password: "password", database: "local", host: "localhost", port: 5432, }, vpc,});
new sst.aws.Function("MyFunction", { vpc, url: true, link: [rds], handler: "index.handler",});
View the full example.
Prisma in Lambda
To use Prisma in a Lambda function you need to
- Generate the Prisma Client with the right architecture
- Copy the generated client to the function
- Run the function inside a VPC
You can set the architecture using the binaryTargets
option in prisma/schema.prisma
.
// For x86binaryTargets = ["native", "rhel-openssl-3.0.x"]// For ARM// binaryTargets = ["native", "linux-arm64-openssl-3.0.x"]
You can also switch to ARM, just make sure to also change the function architecture in your
sst.config.ts
.
{ // For ARM architecture: "arm64"}
To generate the client, you need to run prisma generate
when you make changes to the
schema.
Since this needs to be done on every deploy, we add a postinstall
script to the package.json
.
"scripts": { "postinstall": "prisma generate"}
This runs the command on npm install
.
We then need to copy the generated client to the function when we deploy.
{ copyFiles: [{ from: "node_modules/.prisma/client/" }]}
Our function also needs to run inside a VPC, since Prisma doesn’t support the Data API.
{ vpc}
Prisma in serverless environments
Prisma is not great in serverless environments. For a couple of reasons:
- It doesn’t support Data API, so you need to manage the connection pool on your own.
- Without the Data API, your functions need to run inside a VPC.
- You cannot use
sst dev
without connecting to the VPC.
- You cannot use
- 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.
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], // For ARM // architecture: "arm64", handler: "index.handler", copyFiles: [{ from: "node_modules/.prisma/client/" }],});
return { api: api.url,};
View the full example.
Puppeteer in Lambda
To use Puppeteer in a Lambda function you need:
puppeteer-core
- 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.
- In
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
.
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.
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.
// This is the path to the local Chromium binaryconst 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.
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
.
{ 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.
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.
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.
AWS Redis local
In this example, we connect to a local Docker Redis instance for dev. While on deploy, we use Redis ElastiCache.
We use the docker run
CLI
to start a local Redis server. You don’t have to use Docker, you can run it locally any way
you want.
docker run \ --rm \ -p 6379:6379 \ -v $(pwd)/.sst/storage/redis:/data \ redis:latest
The data is persisted to the .sst/storage
directory. So if you restart the dev server,
the data will still be there.
We then configure the dev
property of the Redis
component with the settings for the
local Redis server.
dev: { host: "localhost", port: 6379}
By providing the dev
prop for Redis, SST will use the local Redis server and
not deploy a new Redis ElastiCache cluster when running sst dev
.
It also allows us to access Redis through a Reosurce link
.
const client = Resource.MyRedis.host === "localhost" ? new Redis({ host: Resource.MyRedis.host, port: Resource.MyRedis.port, }) : new Cluster( [{ host: Resource.MyRedis.host, port: Resource.MyRedis.port, }], { redisOptions: { tls: { checkServerIdentity: () => undefined }, username: Resource.MyRedis.username, password: Resource.MyRedis.password, }, }, );
The local Redis server is running in standalone
mode, whereas on deploy it’ll be in
cluster
mode. So our Lambda function needs to connect using the right config.
const vpc = new sst.aws.Vpc("MyVpc", { nat: "managed" });
const redis = new sst.aws.Redis("MyRedis", { dev: { host: "localhost", port: 6379, }, vpc,});
new sst.aws.Function("MyApp", { vpc, url: true, link: [redis], handler: "index.handler",});
View the full example.
AWS Remix container with Redis
Creates a hit counter app with Remix and Redis.
This deploys Remix as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:5173
you’ll see a counter update as you refresh the page.
Finally, you can deploy it by adding the Dockerfile
that’s included in this example and
running npx sst deploy --stage production
.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
View the full example.
AWS Remix streaming
Follows the Remix Streaming guide to create an app that streams data.
Uses the defer
utility to stream data through the loader
function.
return defer({ spongebob, friends: friendsPromise,});
Then uses the the Suspense
and Await
components to render the data.
<Suspense fallback={<div>Loading...</div>}> <Await resolve={friends}> { /* ... */ } </Await></Suspense>
You should see the friends section load after a 3 second delay.
Safari uses a different heuristic to determine when to stream data. You need to render enough initial HTML to trigger streaming. This is typically only a problem for demo apps.
Streaming works out of the box with the Remix
component.
new sst.aws.Remix("MyWeb");
View the full example.
Router and bucket
Creates a router that serves static files from the public
folder of a given bucket.
// Create a bucket that CloudFront can accessconst bucket = new sst.aws.Bucket("MyBucket", { access: "cloudfront",});
// Upload the image to the `public` foldernew aws.s3.BucketObjectv2("MyImage", { bucket: bucket.name, key: "public/spongebob.svg", contentType: "image/svg+xml", source: $asset("spongebob.svg"),});
const router = new sst.aws.Router("MyRouter", { routes: { "/*": { bucket, rewrite: { regex: "^/(.*)$", to: "/public/$1" }, }, },});
return { image: $interpolate`${router.url}/spongebob.svg`,};
View the full example.
Router and function URL
Creates a router that routes all requests to a function with a URL.
const api = new sst.aws.Function("MyApi", { handler: "api.handler", url: true,});const bucket = new sst.aws.Bucket("MyBucket", { access: "public",});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.
AWS Cluster Service Discovery
In this example, we are connecting to a service running on a cluster using its AWS Cloud Map service host name. This is useful for service discovery.
We are deploying a service to a cluster in a VPC. And we can access it within the VPC using the service’s cloud map hostname.
const reponse = await fetch(`http://${Resource.MyService.service}`);
Here we are accessing it through a Lambda function that’s linked to the service and is deployed to the same VPC.
const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });
const cluster = new sst.aws.Cluster("MyCluster", { vpc });const service = new sst.aws.Service("MyService", { cluster });
new sst.aws.Function("MyFunction", { vpc, url: true, link: [service], handler: "lambda.handler",});
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.
{ 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.
{ "dependencies": { "sharp": "^0.33.5" }}
On deploy, SST will use the right binary from the sharp package for the target Lambda architecture.
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.
AWS SolidStart WebSocket endpoint
Deploys a SolidStart app with a WebSocket endpoint in a container to AWS.
Uses the experimental WebSocket support in Nitro.
export default defineConfig({ server: { experimental: { websocket: true, }, },}).addRouter({ name: "ws", type: "http", handler: "./src/ws.ts", target: "server", base: "/ws",});
Once deployed you can test the /ws
endpoint and it’ll send a message back after a 3s delay.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
View the full example.
AWS static site basic auth
This deploys a simple static site and adds basic auth to it.
This is useful for dev environments where you want to share a static site with your team but ensure that it’s not publicly accessible.
This works by injecting some code into a CloudFront function that checks the basic auth
header and matches it against the USERNAME
and PASSWORD
secrets.
{ injection: $interpolate` if ( !event.request.headers.authorization || event.request.headers.authorization.value !== "Basic ${basicAuth}" ) { return { statusCode: 401, headers: { "www-authenticate": { value: "Basic" } } }; }`,}
To deploy this, you need to first set the USERNAME
and PASSWORD
secrets.
sst secret set USERNAME my-usernamesst secret set PASSWORD my-password
If you are deploying this to preview environments, you might want to set the secrets using
the --fallback
flag.
const username = new sst.Secret("USERNAME");const password = new sst.Secret("PASSWORD");const basicAuth = $resolve([username.value, password.value]).apply( ([username, password]) => Buffer.from(`${username}:${password}`).toString("base64"));
new sst.aws.StaticSite("MySite", { path: "site", // Don't password protect prod edge: $app.stage !== "production" ? { viewerRequest: { injection: $interpolate` if ( !event.request.headers.authorization || event.request.headers.authorization.value !== "Basic ${basicAuth}" ) { return { statusCode: 401, headers: { "www-authenticate": { value: "Basic" } } }; }`, }, } : undefined,});
View the full example.
AWS static site
Deploy a simple HTML file as a static site with S3 and CloudFront. The website is stored in
the site/
directory.
new sst.aws.StaticSite("MySite", { path: "site",});
View the full example.
AWS SvelteKit container with Redis
Creates a hit counter app with SvelteKit and Redis.
This deploys SvelteKit as a Fargate service to ECS and it’s linked to Redis.
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
Since our Redis cluster is in a VPC, we’ll need a tunnel to connect to it from our local machine.
sudo npx sst tunnel install
This needs sudo to create a network interface on your machine. You’ll only need to do this once on your machine.
To start your app locally run.
npx sst dev
Now if you go to http://localhost:5173
you’ll see a counter update as you refresh the page.
Finally, you can deploy it by adding the Dockerfile
that’s included in this example and
running npx sst deploy --stage production
.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true });const redis = new sst.aws.Redis("MyRedis", { vpc });const cluster = new sst.aws.Cluster("MyCluster", { vpc });
new sst.aws.Service("MyService", { cluster, link: [redis], loadBalancer: { ports: [{ listen: "80/http", forward: "3000/http" }], }, dev: { command: "npm run dev", },});
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.
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.
T3 Stack in AWS
Deploy T3 stack with Drizzle and Postgres to AWS.
This example was created using create-t3-app
and the following options: tRPC, Drizzle,
no auth, Tailwind, Postgres, and the App Router.
Instead of a local database, we’ll be using an RDS Postgres database.
const pool = new Pool({ host: Resource.MyPostgres.host, port: Resource.MyPostgres.port, user: Resource.MyPostgres.username, password: Resource.MyPostgres.password, database: Resource.MyPostgres.database,});
Similarly, for Drizzle Kit.
export default { schema: "./src/server/db/schema.ts", dialect: "postgresql", dbCredentials: { ssl: { rejectUnauthorized: false, }, host: Resource.MyPostgres.host, port: Resource.MyPostgres.port, user: Resource.MyPostgres.username, password: Resource.MyPostgres.password, database: Resource.MyPostgres.database, }, tablesFilter: ["aws-t3_*"],} satisfies Config;
In our Next.js app we can access our Postgres database because we link them
both. We don’t need to use our .env
files.
const rds = new sst.aws.Postgres("MyPostgres", { vpc, proxy: true });
new sst.aws.Nextjs("MyWeb", { vpc, link: [rds] });
To run this in dev mode run:
npm installnpx sst dev
It’ll take a few minutes to deploy the database and the VPC.
This also starts a tunnel to let your local machine connect to the RDS Postgres database. Make sure you have it installed, you only need to do this once for your local machine.
sudo npx sst tunnel install
Now in a new terminal you can run the database migrations.
npm run db:push
We also have the Drizzle Studio start automatically in dev mode under the Studio tab.
new sst.x.DevCommand("Studio", { link: [rds], dev: { command: "npx drizzle-kit studio", },});
And to make sure our credentials are available, we update our package.json
with the sst shell
CLI.
"db:generate": "sst shell drizzle-kit generate","db:migrate": "sst shell drizzle-kit migrate","db:push": "sst shell drizzle-kit push","db:studio": "sst shell drizzle-kit studio",
So running npm run db:push
will run Drizzle Kit with the right credentials.
To deploy this to production run:
npx sst deploy --stage production
Then run the migrations.
npx sst shell --stage production npx drizzle-kit push
If you are running this locally, you’ll need to have a tunnel running.
npx sst tunnel --stage production
If you are doing this in a CI/CD pipeline, you’d want your build containers to be in the same VPC.
const vpc = new sst.aws.Vpc("MyVpc", { bastion: true, nat: "ec2" });const rds = new sst.aws.Postgres("MyPostgres", { vpc, proxy: true });
new sst.aws.Nextjs("MyWeb", { vpc, link: [rds]});
new sst.x.DevCommand("Studio", { link: [rds], dev: { command: "npx drizzle-kit studio", },});
View the full example.
AWS Task Cron
Use the Task
and Cron
components
for long running background tasks.
We have a node script that we want to run in index.mjs
. It’ll be deployed as a
Docker container using Dockerfile
.
It’ll be invoked by a cron job that runs every 2 minutes.
new sst.aws.Cron("MyCron", { task, schedule: "rate(2 minutes)"});
When this is run in sst dev
, the task is executed locally using dev.command
.
dev: { command: "node index.mjs"}
To deploy, you need the Docker daemon running.
const bucket = new sst.aws.Bucket("MyBucket");const vpc = new sst.aws.Vpc("MyVpc");
const cluster = new sst.aws.Cluster("MyCluster", { vpc });const task = new sst.aws.Task("MyTask", { cluster, link: [bucket], dev: { command: "node index.mjs", },});
new sst.aws.Cron("MyCron", { task, schedule: "rate(2 minutes)",});
View the full example.
AWS Task
Use the Task
component to run background tasks.
We have a node script that we want to run in image/index.mjs
. It’ll be deployed as a
Docker container using image/Dockerfile
.
We also have a function that the task is linked to. It uses the SDK to start the task.
import { Resource } from "sst";import { task } from "sst/aws/task";
export const handler = async () => { const ret = await task.run(Resource.MyTask); return { statusCode: 200, body: JSON.stringify(ret, null, 2), };};
When this is run in sst dev
, the task is executed locally using dev.command
.
dev: { command: "node index.mjs"}
To deploy, you need the Docker daemon running.
const bucket = new sst.aws.Bucket("MyBucket");const vpc = new sst.aws.Vpc("MyVpc", { nat: "ec2" });
const cluster = new sst.aws.Cluster("MyCluster", { vpc });
const task = new sst.aws.Task("MyTask", { cluster, link: [bucket], image: { context: "image", }, dev: { command: "node index.mjs", },});
new sst.aws.Function("MyApp", { vpc, url: true, link: [task], handler: "index.handler",});
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.
const queue = new sst.aws.Queue("MyQueue");queue.subscribe("subscriber.handler");
const topic = new sst.aws.SnsTopic("MyTopic");topic.subscribe("MySubscriber1", "subscriber.handler", {});topic.subscribeQueue("MySubscriber2", 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.
Vector search
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.
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.
new sst.aws.StaticSite("Web", { build: { command: "pnpm run build", output: "dist", },});
View the full example.
Cloudflare Cron
This example creates a Cloudflare Worker that runs on a schedule.
const cron = new sst.cloudflare.Cron("Cron", { job: "index.ts", schedules: ["* * * * *"]});
return {};
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.
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.
Link multiple secrets
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.
// Manage all secrets togetherconst 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
.
$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.
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.