Serverless Deployment (AWS Lambda)
Guren runs on AWS Lambda via the @guren/core/lambda adapter. This guide covers the full serverless stack — HTTP, queues, scheduled tasks, CLI commands, and infrastructure.
Serverless Deployment (AWS Lambda)
Guren runs on AWS Lambda via the @guren/core/lambda adapter. This guide covers the full serverless stack — HTTP, queues, scheduled tasks, CLI commands, and infrastructure.
Complete Lambda Entry Point
A single lambda.ts file can export handlers for every Lambda function:
import app from './src/app'
import {
createLambdaHandler,
createSqsHandler,
createScheduleHandler,
createConsoleHandler,
} from '@guren/core/lambda'
import { scheduler } from './src/scheduler'
import { kernel } from './src/console'
await app.boot()
// HTTP requests via API Gateway / ALB
export const http = createLambdaHandler(app)
// SQS queue job processing
export const queue = createSqsHandler()
// EventBridge scheduled tasks
export const schedule = createScheduleHandler(scheduler)
// CLI commands (migrations, seeders, etc.)
export const console = createConsoleHandler(kernel)
app.boot() runs once during cold start. All handlers share the booted application instance.
Handlers
HTTP — createLambdaHandler(app)
Wraps the Hono fetch handler for API Gateway v1/v2 and ALB. Routes, controllers, and middleware work identically to the serverful configuration.
Queue — createSqsHandler()
Processes SQS messages as Guren jobs. Supports partial batch failure — only failed messages are returned to SQS for retry.
Configure the SQS driver in your queue provider:
import { SQSClient } from '@aws-sdk/client-sqs'
import { createSqsAdapter, SqsDriver, setQueueDriver } from '@guren/core'
const adapter = createSqsAdapter(new SQSClient({ region: 'ap-northeast-1' }))
setQueueDriver(new SqsDriver(adapter, {
queueUrl: process.env.SQS_QUEUE_URL!,
// Optional: map logical queue names to separate SQS URLs
queueUrls: {
emails: process.env.SQS_EMAILS_QUEUE_URL!,
},
}))
Jobs are dispatched the same way as on the server — await SendEmailJob.dispatch({ to: 'user@example.com' }). The SqsDriver serializes the job to SQS, and the Lambda handler deserializes and executes it.
Schedule — createScheduleHandler(scheduler)
Runs due tasks when invoked by EventBridge. Configure an EventBridge rule with rate(1 minute) to trigger this handler. The existing Scheduler and task definitions work without changes.
Console — createConsoleHandler(kernel)
Executes CLI commands on Lambda. Invoke via AWS CLI:
aws lambda invoke --function-name my-app-console \
--payload '{"command": "db:migrate"}' response.json
Returns { exitCode: 0 } on success, { exitCode: 1 } on failure.
Runtime Detection
Conditionally configure services based on the runtime:
import { isLambda, getLambdaContext } from '@guren/core/lambda'
if (isLambda()) {
const ctx = getLambdaContext()!
// ctx.functionName — Lambda function name
// ctx.region — AWS region
// ctx.memorySize — allocated memory (MB)
// ctx.tmpDir — writable temp directory (/tmp)
// ctx.logGroup — CloudWatch log group
}
Password Hashing
The default ScryptHasher uses Bun.password — unavailable on Lambda's Node.js runtime. Use NodeHasher instead:
import { NodeHasher } from '@guren/core'
// In your AuthProvider:
container.instance('hash', new NodeHasher())
Warning
ScryptHasher (Argon2/bcrypt via Bun) and NodeHasher (scrypt via Node.js) produce incompatible hash formats. Migrating an existing Bun-based app to Lambda requires rehashing passwords or a multi-format verifier.
Logging
Lambda captures stderr to CloudWatch automatically. Use JSON-formatted console logging:
import { LogManager } from '@guren/core'
const log = new LogManager({
default: 'console',
channels: {
console: { driver: 'console', level: 'info', format: 'json', colors: false },
},
})
Static Assets
Lambda is not suited for serving static files. Use CloudFront + S3:
- Upload Vite build output (
public/assets/) to an S3 bucket. - Create a CloudFront distribution pointing to that bucket.
- Set
GUREN_INERTIA_ENTRY_URLandGUREN_INERTIA_STYLES_URLto your CloudFront URLs.
Configuration Notes
Service Providers
Auto-discovery (Bun.Glob) is not available on Lambda. List all providers explicitly:
const app = createApp({
providers: [
DatabaseProvider,
AuthProvider,
CacheProvider,
// ... all providers
],
routes: registerRoutes,
})
Storage & Filesystem
Lambda has a read-only filesystem except for /tmp (512 MB, ephemeral). Use /tmp only for transient cache. For persistent storage, use S3 via the S3Driver.
Sessions & Cache
In-memory stores are lost between invocations. Use Redis (ElastiCache) or DynamoDB for sessions and cache.
Infrastructure Recommendations
| Concern | Recommendation |
|---|---|
| HTTP trigger | API Gateway v2 (HTTP API) or ALB |
| Sessions | Redis (ElastiCache) — not in-memory |
| Cache | Redis via RedisCacheStore (@guren/core/redis ships session/rate-limit/API-token stores too), or FileStore with /tmp for ephemeral cache |
| Queue | SQS via SqsDriver + createSqsHandler() |
| Database | RDS PostgreSQL with RDS Proxy for connection pooling |
| Scheduling | EventBridge + createScheduleHandler() |
| CLI commands | Dedicated Lambda + createConsoleHandler() |
| Static assets | CloudFront + S3 |
| Logging | CloudWatch (stderr, JSON format) |
AWS CDK Example
A complete CDK stack with all four Lambda functions:
import { Duration, Stack } from 'aws-cdk-lib'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as apigw from 'aws-cdk-lib/aws-apigatewayv2'
import * as sqs from 'aws-cdk-lib/aws-sqs'
import * as events from 'aws-cdk-lib/aws-events'
import * as targets from 'aws-cdk-lib/aws-events-targets'
import * as sqsEventSources from 'aws-cdk-lib/aws-lambda-event-sources'
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'
const code = lambda.Code.fromAsset('dist')
const runtime = lambda.Runtime.NODEJS_22_X
const environment = {
NODE_ENV: 'production',
DATABASE_URL: '...',
SQS_QUEUE_URL: '', // set below
}
// --- HTTP ---
const httpFn = new lambda.Function(this, 'Http', {
runtime,
handler: 'lambda.http',
code,
timeout: Duration.seconds(30),
memorySize: 512,
environment,
})
const api = new apigw.HttpApi(this, 'Api')
api.addRoutes({
path: '/{proxy+}',
integration: new HttpLambdaIntegration('HttpIntegration', httpFn),
})
// --- Queue ---
const queue = new sqs.Queue(this, 'JobQueue', {
visibilityTimeout: Duration.seconds(60),
deadLetterQueue: {
queue: new sqs.Queue(this, 'JobDLQ'),
maxReceiveCount: 3,
},
})
environment.SQS_QUEUE_URL = queue.queueUrl
const queueFn = new lambda.Function(this, 'QueueWorker', {
runtime,
handler: 'lambda.queue',
code,
timeout: Duration.seconds(60),
memorySize: 512,
environment,
})
queueFn.addEventSource(
new sqsEventSources.SqsEventSource(queue, {
batchSize: 10,
reportBatchItemFailures: true,
}),
)
// --- Scheduler ---
const scheduleFn = new lambda.Function(this, 'Scheduler', {
runtime,
handler: 'lambda.schedule',
code,
timeout: Duration.seconds(60),
memorySize: 256,
environment,
})
new events.Rule(this, 'ScheduleRule', {
schedule: events.Schedule.rate(Duration.minutes(1)),
targets: [new targets.LambdaFunction(scheduleFn)],
})
// --- Console ---
const consoleFn = new lambda.Function(this, 'Console', {
runtime,
handler: 'lambda.console',
code,
timeout: Duration.minutes(5),
memorySize: 512,
environment,
})
Note
Bundle your application with esbuild or tsup before deploying. Ensure all dependencies are included in the deployment package.