sls-ts
v0.1.1
Published
Type-safe infrastructure configuration for Serverless Framework
Downloads
6
Maintainers
Readme
sls-ts
Type-safe infrastructure configuration for osls (the maintained Serverless Framework v3 fork). Write your serverless config in TypeScript, get the YAML you already know works.
Why?
You shouldn't have to choose between type safety and a proven deployment tool. The Serverless Framework works. It deploys reliably. It has plugins that actually function. But YAML duplication across multiple apps is a nightmare.
sls-ts gives you type-safe constructs, generates your serverless.yml, and wraps the dev/deploy workflow.
What it does
- Type-safe constructs for AWS resources (API Gateway, DynamoDB, Cognito, S3, SQS, SNS, SES, WebSocket, EventBridge)
- Generates
serverless.ymlfrom TypeScript config - Wraps dev/deploy/remove workflow with a single CLI
- Generates frontend
.envfiles from your config - Works with osls and existing plugins
- Lets you iterate locally without touching AWS
What it doesn't do
- Replace osls/Serverless Framework (it uses it)
- Replace serverless-offline (it depends on it)
- Abstract away CloudFormation (you still see the resources)
- Add vendor lock-in (it's just a code generator)
Installation
npm install --save-dev sls-ts
npx sls-ts init # Installs osls and required pluginsQuick Start
Create infra/config.ts:
import { defineConfig, staticSite, api, dynamoTable, cognitoAuth, sqs, sns, schedule, s3Bucket, ses, websocket, eventBridge } from 'sls-ts';
export default defineConfig({
app: 'my-app',
auth: cognitoAuth({
emailLogin: true
}),
tables: {
users: dynamoTable({
pk: 'userId',
sk: 'timestamp'
}),
},
api: api({
routes: {
'GET /users': 'src/handlers/getUsers.handler',
'POST /users': 'src/handlers/createUser.handler',
},
authorizer: 'cognito',
}),
sites: {
admin: staticSite({
path: 'apps/admin',
buildOutput: '.dist'
}),
web: staticSite({
path: 'apps/web',
buildOutput: '.dist'
}),
},
});Run locally:
npx sls-ts devDeploy:
npx sls-ts deploy --stage staging
npx sls-ts deploy --stage prodConstructs
staticSite(options)
Generates S3 + CloudFront configuration for static site hosting.
staticSite({
path: 'apps/web', // Path to your app
buildOutput: '.dist', // Build output directory
domain: { // Optional custom domain
domain: 'www.example.com',
certificateArn: 'arn:aws:acm:us-east-1:...',
hostedZoneId: 'Z1234...', // Creates Route53 A record
},
})Generates:
- S3 bucket with proper policies
- CloudFront distribution with OAC
- ACM certificate attachment
- Route53 A record (if
hostedZoneIdprovided) - Cache policies and output URLs
api(options)
Generates API Gateway HTTP API + Lambda configuration.
api({
routes: {
// Simple handler string
'GET /users': 'src/handlers/getUsers.handler',
// Or with Lambda config
'POST /users': {
handler: 'src/handlers/createUser.handler',
timeout: 30,
memorySize: 512,
},
},
authorizer: 'cognito', // Optional JWT authorizer
cors: true, // Enable CORS
defaults: { // Default Lambda config for all routes
timeout: 10,
memorySize: 1024,
},
domain: { // Optional custom domain
domain: 'api.example.com',
certificateArn: 'arn:aws:acm:us-east-1:...',
hostedZoneId: 'Z1234...', // Creates Route53 A record
},
})Generates:
- Lambda functions for each route
- HTTP API with optional JWT authorizer
- Custom domain with API mapping (if configured)
- Route53 A record (if
hostedZoneIdprovided)
dynamoTable(options)
Generates DynamoDB table configuration.
dynamoTable({
pk: 'userId', // Partition key
sk: 'timestamp', // Optional sort key
gsi: [ // Optional GSIs
{ pk: 'email', name: 'EmailIndex' }
],
ttl: 'expiresAt', // TTL attribute (auto-delete expired items)
stream: 'NEW_AND_OLD_IMAGES', // DynamoDB Streams for triggers
localSeed: './seed/users.json', // Optional seed data for local dev
})Generates:
- DynamoDB table with PAY_PER_REQUEST billing
- TTL configuration for automatic item expiration
- Stream configuration for event-driven triggers
- IAM permissions for Lambda functions
- Environment variable with table name
- Local DynamoDB configuration with seeding
cognitoAuth(options)
Generates Cognito User Pool and Client.
cognitoAuth({
emailLogin: true,
selfSignUp: true, // Allow users to register (default: true)
emailVerification: true, // Require email verification (default: true)
mfa: 'OPTIONAL', // 'OFF' | 'OPTIONAL' | 'ON' (default: 'OFF')
passwordPolicy: {
minLength: 12,
requireNumbers: true,
requireSymbols: true,
requireUppercase: true,
requireLowercase: true,
},
})selfSignUp: false- Admin-only account creation (internal tools)mfa: 'ON'- Require MFA for all users (high-security apps)
sqs(options)
Generates SQS queues with Lambda consumers.
sqs({
handler: 'src/handlers/processJob.handler', // Consumer function
batchSize: 10, // Messages per invocation (default: 10)
maxBatchingWindow: 5, // Seconds to wait for batch (default: 0)
visibilityTimeout: 30, // Seconds before retry (default: 30)
dlq: true, // Enable dead-letter queue
// dlq: { maxReceiveCount: 5 } // Custom retry count
timeout: 30, // Lambda timeout
memorySize: 512, // Lambda memory
})Generates:
- SQS queue with configurable settings
- Dead-letter queue (optional)
- Lambda consumer with SQS trigger
- IAM permissions and environment variables (
QUEUE_NAME_URL)
sns(options)
Generates SNS topics with optional Lambda subscribers.
sns() // Topic only, no subscriber
sns({
handler: 'src/handlers/notify.handler' // With subscriber
})Generates:
- SNS topic
- Lambda subscriber (optional)
- IAM permissions and environment variables (
TOPIC_NAME_ARN)
schedule(options)
Generates EventBridge scheduled functions.
schedule({
handler: 'src/handlers/cleanup.handler',
rate: 'rate(1 hour)', // OR cron expression
// cron: 'cron(0 12 * * ? *)', // Daily at noon UTC
input: { type: 'full' }, // Optional payload
enabled: true, // Default: true
timeout: 900, // Lambda timeout (up to 15 min)
memorySize: 2048, // Lambda memory
})s3Bucket(options)
Generates S3 buckets for file storage.
s3Bucket({
cors: true, // Enable CORS for browser uploads
versioning: true, // Enable versioning
expiration: 30, // Auto-delete after N days
handlers: {
onCreate: 'src/handlers/onUpload.handler',
onDelete: 'src/handlers/onDelete.handler',
},
})Generates:
- S3 bucket with public access blocked
- CORS, versioning, lifecycle rules (optional)
- Lambda triggers for S3 events (optional)
- IAM permissions and environment variables (
BUCKET_NAME)
ses(options)
Generates SES email identity for transactional emails.
ses({
domain: 'example.com',
verifyDomain: true, // Auto-verify with Route53
hostedZoneId: 'Z123...',
})Generates:
- SES email identity
- DKIM Route53 records (if
verifyDomain+hostedZoneId) - IAM permissions and environment variable (
SES_FROM_DOMAIN)
websocket(options)
Generates WebSocket API Gateway for real-time features.
websocket({
routes: {
$connect: 'src/handlers/connect.handler',
$disconnect: 'src/handlers/disconnect.handler',
$default: 'src/handlers/default.handler',
sendMessage: 'src/handlers/sendMessage.handler',
},
})Generates:
- WebSocket API with route selection
- Lambda functions for each route
- IAM permissions for
ManageConnections - Environment variables (
WEBSOCKET_API_ID,WEBSOCKET_ENDPOINT)
eventBridge(options)
Generates EventBridge rules for event-driven architecture.
eventBridge({
handler: 'src/handlers/onOrderCreated.handler',
pattern: {
source: ['my.app'],
'detail-type': ['order.created'],
detail: { status: ['confirmed'] },
},
bus: 'orders', // Optional custom bus (default: 'default')
enabled: true, // Default: true
})Generates:
- Lambda function triggered by events
- Custom EventBus resource (if not using default)
- IAM permissions for
events:PutEvents
ssm(path, decrypt?)
Reference AWS SSM Parameter Store values for secrets and configuration.
import { ssm } from 'sls-ts';
ssm('/myapp/prod/api-key') // SecureString, auto-decrypted
ssm('/myapp/prod/config', false) // Plain String, no decryptionMulti-Environment Configuration
Pass a function to defineConfig to customize configuration per stage:
import { defineConfig, staticSite, api, cognitoAuth } from 'sls-ts';
export default defineConfig((stage) => ({
app: 'my-app',
auth: cognitoAuth({
emailLogin: true,
passwordPolicy: stage === 'prod'
? { minLength: 12, requireSymbols: true }
: { minLength: 8 },
}),
sites: {
web: staticSite({
path: 'apps/web',
buildOutput: '.dist',
// Custom domain only in prod
domain: stage === 'prod' ? {
domain: 'www.example.com',
certificateArn: 'arn:aws:acm:us-east-1:123456789:certificate/xxx',
} : undefined,
}),
},
}));Generate for each environment:
npx sls-ts generate --stage dev
npx sls-ts generate --stage staging
npx sls-ts generate --stage prodEnvironment Variables & Secrets
Use the env option to define environment variables. Combine with ssm() for secrets:
import { defineConfig, api, ssm } from 'sls-ts';
export default defineConfig((stage) => ({
app: 'my-app',
env: {
NODE_ENV: stage,
LOG_LEVEL: stage === 'prod' ? 'error' : 'debug',
// Secrets from SSM Parameter Store
DATABASE_URL: ssm(`/myapp/${stage}/database-url`),
API_KEY: ssm(`/myapp/${stage}/api-key`),
STRIPE_SECRET: ssm(`/myapp/${stage}/stripe-secret`),
},
api: api({
routes: {
'POST /checkout': 'src/handlers/checkout.handler',
},
}),
}));This generates:
provider:
environment:
NODE_ENV: prod
LOG_LEVEL: error
DATABASE_URL: ${ssm:/myapp/prod/database-url~true}
API_KEY: ${ssm:/myapp/prod/api-key~true}
STRIPE_SECRET: ${ssm:/myapp/prod/stripe-secret~true}Create your SSM parameters:
aws ssm put-parameter \
--name "/myapp/prod/database-url" \
--value "postgres://user:pass@host:5432/db" \
--type SecureString
aws ssm put-parameter \
--name "/myapp/prod/api-key" \
--value "sk_live_..." \
--type SecureStringosls resolves these at deploy time—secrets never touch your codebase or CI logs.
Custom Domains & SSL
Configure custom domains with ACM certificates and automatic Route53 records:
import { defineConfig, staticSite, api } from 'sls-ts';
// Reusable domain config
const cert = 'arn:aws:acm:us-east-1:123456789:certificate/abc-123';
const hostedZoneId = 'Z1234567890ABC';
export default defineConfig((stage) => ({
app: 'my-app',
api: api({
routes: { 'GET /health': 'src/handlers/health.handler' },
domain: stage === 'prod' ? {
domain: 'api.example.com',
certificateArn: cert,
hostedZoneId,
} : undefined,
}),
sites: {
web: staticSite({
path: 'apps/web',
buildOutput: '.dist',
domain: stage === 'prod' ? {
domain: 'www.example.com',
certificateArn: cert,
hostedZoneId,
} : undefined,
}),
},
}));Prerequisites:
ACM Certificate - Must be in
us-east-1for CloudFront:aws acm request-certificate \ --domain-name "*.example.com" \ --validation-method DNS \ --region us-east-1Route53 Hosted Zone - Get the ID:
aws route53 list-hosted-zones-by-name \ --dns-name example.com \ --query 'HostedZones[0].Id' \ --output text
When hostedZoneId is provided, sls-ts generates Route53 A records pointing to your CloudFront distribution or API Gateway domain.
Local Development
Generate Environment Files
Generate .env files for your frontend apps:
# Generate .env.local for local development
npx sls-ts env --stage local
# Generate .env.prod with production URLs
npx sls-ts env --stage prodThis creates .env.{stage} files in each site's directory with:
# apps/web/.env.local
VITE_API_URL=http://localhost:3001
VITE_USER_POOL_ID=local_user_pool
VITE_USER_POOL_CLIENT_ID=local_client
VITE_STAGE=local
# Also generates NEXT_PUBLIC_* variants for Next.jsRunning Locally
# Start everything with one command
npx sls-ts dev
# This automatically:
# 1. Generates serverless.yml
# 2. Generates .env files for frontends
# 3. Starts serverless-offlineOr add to your package.json:
{
"scripts": {
"dev": "sls-ts dev",
"dev:web": "cd apps/web && npm run dev",
"deploy:staging": "sls-ts deploy --stage staging",
"deploy:prod": "sls-ts deploy --stage prod"
}
}Run API and frontend together:
# Terminal 1: API
npm run dev
# Terminal 2: Frontend
npm run dev:webLocal DynamoDB with Seed Data
sls-ts automatically configures local DynamoDB when you have tables. Add seed data for realistic local testing:
1. Define tables with seed files:
// infra/config.ts
export default defineConfig({
app: 'my-app',
tables: {
users: dynamoTable({
pk: 'userId',
sk: 'createdAt',
localSeed: './seed/users.json',
}),
posts: dynamoTable({
pk: 'postId',
localSeed: './seed/posts.json',
}),
},
});2. Create seed files:
// seed/users.json
[
{
"userId": "user-1",
"createdAt": "2024-01-01T00:00:00Z",
"email": "[email protected]",
"name": "Alice"
},
{
"userId": "user-2",
"createdAt": "2024-01-02T00:00:00Z",
"email": "[email protected]",
"name": "Bob"
}
]// seed/posts.json
[
{
"postId": "post-1",
"authorId": "user-1",
"title": "Hello World",
"content": "This is a test post"
}
]3. Run locally:
# Install local DynamoDB (first time only)
npx osls dynamodb install
# Start API with seeded database
npx sls-ts devThe seed data is automatically loaded when DynamoDB Local starts. Tables are created in-memory and reset on each restart.
Local Auth Mocking
For local development, the generated env files use placeholder Cognito values. Your frontend can check VITE_STAGE === 'local' to bypass real auth:
// src/auth.ts
const isLocal = import.meta.env.VITE_STAGE === 'local';
export async function getAuthToken(): Promise<string | null> {
if (isLocal) {
// Return a mock JWT for local development
return 'eyJ...local-mock-token';
}
// Real Cognito auth flow
return await Auth.currentSession().getIdToken().getJwtToken();
}Plugin Support
sls-ts works with standard osls/Serverless Framework plugins:
serverless-offline- Local Lambda executionserverless-dynamodb- Local DynamoDBserverless-s3-sync- S3 deploymentserverless-cloudfront-invalidate- Cache invalidation- Any other plugin that reads
serverless.yml
TypeScript Benefits
// Autocomplete for routes
api({
routes: {
'GET /users': 'src/handlers/getUsers.handler', // ← IDE validates path
}
})
// Type-checked table references
dynamoTable({
pk: 'userId', // ← Knows it's a string key
})
// Shared configuration
const commonCacheBehavior = {
compress: true,
defaultTTL: 1800,
};
staticSite({
path: 'apps/web',
cacheBehavior: commonCacheBehavior, // ← Reuse across sites
})Philosophy
No magic. No abstractions you can't debug.
Every construct generates CloudFormation resources you can inspect. If something breaks, you can see exactly what YAML was generated and why.
This isn't trying to replace AWS CDK or Pulumi. It's a thin layer over the tool you already use, adding types and eliminating duplication.
Why osls?
osls is the maintained open-source fork of Serverless Framework v3. It's a drop-in replacement that:
- Supports new AWS Lambda runtimes
- Fixes security vulnerabilities
- Removes Serverless Dashboard/Enterprise bloat
- Is actively maintained by the community
Install globally: npm install -g osls
Comparison
| Tool | Type Safety | Local Dev | Deploys With | Abstracts CFN | |------|-------------|-----------|--------------|---------------| | sls-ts | ✅ | ✅ | osls | No | | osls | ❌ | ✅ | Itself | No | | AWS CDK | ✅ | ❌ | CloudFormation | Yes | | SST | ✅ | ❌ (uses real AWS) | Custom | Yes | | Pulumi | ✅ | ❌ | Custom | Yes |
CLI Reference
# Install osls and required plugins
npx sls-ts init
# Local development (generates config + env files + starts serverless offline)
npx sls-ts dev
npx sls-ts dev --stage dev
# Deploy (generates config + deploys to AWS)
npx sls-ts deploy --stage staging
npx sls-ts deploy --stage prod
npx sls-ts deploy --stage prod --profile prod-account
# Remove deployed stack
npx sls-ts remove --stage staging
npx sls-ts remove --stage prod --profile prod-account
# Generate serverless.yml only
npx sls-ts generate
npx sls-ts generate --stage prod
npx sls-ts generate --config ./custom/config.ts
npx sls-ts generate --output ./services/api/serverless.yml
# Generate .env files for frontends (fetches real URLs from deployed stack)
npx sls-ts env --stage local # Uses localhost
npx sls-ts env --stage prod # Fetches from CloudFormation outputsOptions:
--stage- Deployment stage (default:localfor dev,devfor others)--profile- AWS profile to use--config- Path to config file--output- Output path for serverless.yml
Roadmap
- [x] Core config generator
- [x] Static site construct
- [x] API construct
- [x] DynamoDB construct
- [x] Cognito construct
- [x] SSM Parameter Store support
- [x] Multi-environment configs
- [x] SQS construct (queues with consumers)
- [x] SNS construct (pub/sub topics)
- [x] Schedule construct (EventBridge cron/rate)
- [x] S3 bucket construct (file uploads)
- [x] SES construct (transactional email)
- [x] WebSocket API construct
- [x] EventBridge construct (event rules)
- [ ] Step Functions constructs
- [ ] Custom construct API
Contributing
This project exists because the ecosystem failed to solve a simple problem. PRs welcome, but keep it simple. We're building a code generator, not a framework.
License
MIT
Credits
Built by someone who got tired of duplicating YAML across 5 different serverless.yml files and decided to fix it properly.
Just trying to ship serverless apps without the bullshit.
