Infrastructure
mantle generate infra reads your src/lambdas/ directory and mantle.config.ts and writes OpenTofu (.tf) files to infra/.
Final result
mantle generate infra
# Creates: main.tf, variables.tf, outputs.tf, api_routes.tf
# lambda_<name>.tf per handler
# storage_<name>.tf, database.tf, eventbridge.tf (if configured)Step 1: Configure your project
Declare infrastructure in mantle.config.ts:
import { defineConfig } from '@mantleframework/core'
export default defineConfig({
name: 'my-api',
database: { provider: 'aurora-dsql' },
eventbridge: { bus: 'my-api-events' },
storage: [{ name: 'uploads', cloudfront: true }],
queues: [{ name: 'order-processing', visibilityTimeoutSeconds: 60 }],
})Step 2: Structure your Lambda files
Lambda type is determined by which subdirectory under src/lambdas/ the file lives in:
src/lambdas/
api/ # API Gateway routed (filename = <name>.<method>.ts)
eventbridge/ # EventBridge-triggered
scheduled/ # CloudWatch scheduled
standalone/ # Direct invocation
sqs/ # SQS consumer
s3/ # S3 event-triggered
websocket/ # WebSocket API GatewayAPI handler filenames determine routes and HTTP methods:
| File | Route | Method | Lambda name |
|---|---|---|---|
api/items/index.get.ts | /items | GET | ItemsGet |
api/items/index.post.ts | /items | POST | ItemsPost |
api/items/[id]/index.delete.ts | /items/{id} | DELETE | ItemsIdDelete |
api/ping.get.ts | /ping | GET | Ping |
Use index.<method>.ts when the handler responds at the directory path itself.
Step 3: Run the generator
mantle generate infraGenerated files include a header:
# This file is generated by Mantle CLI
# To customize, remove the first line and this file will be skipped on re-generation.To eject a file (take manual control), remove the first header line. The generator skips ejected files on subsequent runs.
Module structure
All modules accept name_prefix and tags from the core module:
module "core" {
source = "../../../mantle/modules/core"
project_name = "my-api"
environment = var.environment
}
module "api" {
source = "../../../mantle/modules/api-gateway"
name_prefix = module.core.name_prefix
tags = module.core.common_tags
}
module "lambda_get_items" {
source = "../../../mantle/modules/lambda"
name_prefix = module.core.name_prefix
function_name = "GetItems"
region = module.core.region
account_id = module.core.account_id
assume_role_policy = module.core.lambda_assume_role_policy
xray_policy_arn = module.core.lambda_xray_policy_arn
tags = module.core.common_tags
}Available modules:
| Module | Purpose |
|---|---|
modules/core | Naming prefix, common tags, IAM assume-role policies, X-Ray policy |
modules/lambda | Lambda function, IAM role, log group, optional triggers |
modules/api-gateway | REST API with stages, authorizer, usage plans |
modules/queue | SQS queue + DLQ + CloudWatch alarm |
modules/storage | S3 bucket with encryption, versioning, optional CloudFront |
modules/idempotency | DynamoDB table for Lambda Powertools idempotency |
modules/observability | CloudWatch dashboards, alarms, SNS topics |
modules/eventbridge | EventBridge custom event bus |
modules/dynamodb | DynamoDB table with GSIs, TTL, PITR |
modules/websocket-api | WebSocket API Gateway |
modules/database/aurora-dsql | Aurora DSQL cluster |
Resources follow the naming convention ${environment}-${project_name}-ResourceName (PascalCase for logical resources, kebab-case for S3/SQS).
Environment variable auto-wiring
The generator scans handler files for getRequiredEnv() / getOptionalEnv() calls and:
- Wires standard env vars (
API_BEARER_TOKEN,DSQL_ENDPOINT, etc.) from project config - Creates Terraform variables for non-standard env vars
- Resolves
*_BUCKETand*_FUNCTION_NAMEpatterns to module outputs instead of variables
# Auto-generated for getOptionalEnv('LOCATIONIQ_API_KEY', '')
variable "locationiq_api_key" {
description = "LOCATIONIQ_API_KEY environment variable"
type = string
sensitive = true
default = ""
}Variables containing key, token, or secret are automatically marked sensitive = true.
*_BUCKET env vars also generate an inline S3Access IAM policy. *_FUNCTION_NAME env vars generate an Invoke<Name> IAM policy.
Lambda configuration
For EventBridge and scheduled handlers, pass config directly to defineEventBridgeHandler / defineScheduledHandler. Use defineLambda() only for standalone handlers:
import { defineLambda } from '@mantleframework/core'
// Standalone handlers only
defineLambda({
timeout: 60,
memorySize: 256,
deadLetterQueue: true,
retryAttempts: 2,
})| Option | Type | Default | Description |
|---|---|---|---|
timeout | number | — | Timeout in seconds |
memorySize | number | 128 | Memory in MB |
reservedConcurrency | number | — | Reserved concurrent executions |
ephemeralStorage | number | 512 | Ephemeral storage in MB |
deadLetterQueue | boolean | — | true = auto-generate SQS DLQ |
retryAttempts | number | — | Max retry attempts (0–2) |
Ejected files
If you eject main.tf and add new Lambdas, manually add their invoke_arn to the redeployment_trigger hash — the generator silently skips ejected files.
Path parameter directories ([id]) become {id} in the API Gateway path_part and have brackets stripped from Terraform resource identifiers.
Next steps
- Deployment —
mantle buildandmantle deploy - Database — Drizzle schema and Aurora DSQL setup
- Entity Queries —
@RequiresTableandmantle generate permissions