sst-dev
SKILL.md
SST.dev Development Skill
This skill provides comprehensive guidance for working with SST (Serverless Stack), covering infrastructure patterns, best practices, and common use cases.
Core Philosophy
SST embraces these principles:
- Type-safe infrastructure: Full TypeScript support from infrastructure to runtime
- Resource bindings: Type-safe access to resources via
Resource - Convention over configuration: Sensible defaults, customize when needed
- Developer experience: Fast feedback loops, excellent local development
Key Concepts
Resource Bindings
The most powerful SST feature - type-safe resource access:
// In sst.config.ts
const bucket = new sst.aws.Bucket("MyBucket");
const api = new sst.aws.Function("MyApi", {
handler: "src/api.handler",
link: [bucket] // Link the bucket
});
// In src/api.ts
import { Resource } from "sst";
export async function handler() {
// Type-safe access!
await s3.putObject({
Bucket: Resource.MyBucket.name,
// ...
});
}
Key points:
- Use
linkto connect resources - Access via
Resource.[ResourceName] - Full TypeScript autocomplete and type safety
- No environment variables needed
Infrastructure as Code
Define resources in sst.config.ts:
export default $config({
app(input) {
return {
name: "my-app",
removal: input?.stage === "production" ? "retain" : "remove",
};
},
async run() {
// Define your infrastructure
const bucket = new sst.aws.Bucket("Uploads");
const api = new sst.aws.Function("Api", {
handler: "src/api.handler",
link: [bucket],
url: true // Enable function URL
});
return {
api: api.url,
bucket: bucket.name
};
},
});
Common Patterns
Pattern 1: Function with Database Access
// sst.config.ts
const db = new sst.aws.Dynamo("Database", {
fields: {
pk: "string",
sk: "string"
},
primaryIndex: { hashKey: "pk", rangeKey: "sk" }
});
const handler = new sst.aws.Function("Handler", {
handler: "src/handler.main",
link: [db]
});
// src/handler.ts
import { Resource } from "sst";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export async function main(event) {
await client.send(new PutCommand({
TableName: Resource.Database.name,
Item: { pk: "user", sk: "123", data: "..." }
}));
}
Pattern 2: Remix with SST
// sst.config.ts
const bucket = new sst.aws.Bucket("Uploads");
const remix = new sst.aws.Remix("MyApp", {
link: [bucket], // Link resources to Remix
domain: "app.example.com"
});
// In Remix loader/action
import { Resource } from "sst";
export async function loader() {
const url = await getSignedUrl(s3, new GetObjectCommand({
Bucket: Resource.Uploads.name,
Key: "file.pdf"
}));
return { url };
}
Pattern 3: API with Auth
const auth = new sst.aws.Auth("Auth", {
authenticator: "src/auth.handler"
});
const api = new sst.aws.ApiGatewayV2("Api", {
link: [auth],
transform: {
route: {
handler: {
link: [auth]
}
}
}
});
api.route("GET /private", "src/private.handler", {
auth: { iam: true }
});
Pattern 4: Environment-Specific Configuration
export default $config({
app(input) {
return {
name: "my-app",
removal: input?.stage === "production" ? "retain" : "remove",
};
},
async run() {
const isProd = $app.stage === "production";
const db = new sst.aws.Dynamo("Database", {
// Production settings
...(isProd && {
transform: {
table: {
pointInTimeRecovery: { enabled: true }
}
}
})
});
return { stage: $app.stage };
}
});
Resource Types
Compute
Function: Lambda functions with great DXRemix: Remix applicationsNextjs: Next.js applicationsAstro: Astro applications
Storage
Bucket: S3 buckets with automatic policiesDynamo: DynamoDB tables with typed indexes
APIs
ApiGatewayV2: HTTP/WebSocket APIsRouter: Route handling
Auth & Security
Auth: Authentication setupSecret: Secure secret management
Queues & Events
Queue: SQS queuesSnsTopic: SNS topicsEventBus: EventBridge buses
Best Practices
1. Use Resource Bindings Over Environment Variables
❌ Don't:
const tableName = process.env.TABLE_NAME!;
✅ Do:
const tableName = Resource.Database.name;
2. Keep Infrastructure Simple
❌ Don't over-engineer:
// Don't create unnecessary layers
const commonConfig = createConfigBuilder()
.withDefaults()
.withRetries()
.build();
✅ Do keep it simple:
const fn = new sst.aws.Function("Handler", {
handler: "src/handler.main",
timeout: "30 seconds"
});
3. Link Resources Appropriately
Only link what you need:
// If a function only needs the bucket, only link the bucket
const fn = new sst.aws.Function("ProcessUpload", {
handler: "src/process.handler",
link: [bucket] // Not the entire database, auth, etc.
});
4. Use Transforms for AWS-Specific Needs
When you need direct AWS resource access:
new sst.aws.Bucket("Uploads", {
transform: {
bucket: {
lifecycleConfiguration: {
rules: [{
expiration: { days: 30 },
status: "Enabled"
}]
}
}
}
});
5. Type Your Handlers Properly
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
export async function handler(
event: APIGatewayProxyEventV2
): Promise<APIGatewayProxyResultV2> {
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello" })
};
}
6. Organize Large Stacks
// sst.config.ts
async run() {
const storage = await import("./infra/storage");
const api = await import("./infra/api");
const web = await import("./infra/web");
const { bucket, database } = await storage.setup();
const { apiUrl } = await api.setup({ bucket, database });
const { siteUrl } = await web.setup({ apiUrl });
return { apiUrl, siteUrl };
}
Local Development
Running Locally
# Start SST dev mode
sst dev
# In another terminal, run your app
npm run dev
Console Access
# Open SST Console for your stage
sst console
# Deploy to a specific stage
sst deploy --stage production
Testing Resources Locally
SST automatically sets up local versions:
// Works the same locally and deployed
import { Resource } from "sst";
const tableName = Resource.Database.name; // Points to local or deployed based on context
Common Gotchas
1. Resource Name Changes
Renaming resources can cause issues. Use explicit IDs:
// Better: use explicit ID
const bucket = new sst.aws.Bucket("Uploads", {
// Explicit physical name if needed
});
2. Circular Dependencies
Avoid circular links:
❌ // Don't
const fnA = new sst.aws.Function("A", {
link: [fnB]
});
const fnB = new sst.aws.Function("B", {
link: [fnA]
});
✅ // Do: use SNS/SQS or store state in DB
3. Cold Starts
Lambda cold starts are real. Optimize:
// Keep warm-up code outside handler
const client = new DynamoDBClient({});
export async function handler(event) {
// Handler uses pre-initialized client
}
Migration and Updates
Updating SST
npm update sst
# or
pnpm update sst
Breaking Changes
Always check the changelog when upgrading major versions. SST provides migration guides for breaking changes.
Further Reading
- Official SST Docs: https://sst.dev/docs
- SST Examples: https://github.com/sst/examples
- SST Discord: Great for questions and support
Weekly Installs
9
Repository
tejovanthn/rasikalifeFirst Seen
12 days ago
Security Audits
Installed on
opencode9
gemini-cli9
claude-code9
github-copilot9
codex9
kimi-cli9