Skip to content

Infrastructure

mantle generate infra reads your src/lambdas/ directory and mantle.config.ts and writes OpenTofu (.tf) files to infra/.

Final result

bash
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:

typescript
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 Gateway

API handler filenames determine routes and HTTP methods:

FileRouteMethodLambda name
api/items/index.get.ts/itemsGETItemsGet
api/items/index.post.ts/itemsPOSTItemsPost
api/items/[id]/index.delete.ts/items/{id}DELETEItemsIdDelete
api/ping.get.ts/pingGETPing

Use index.<method>.ts when the handler responds at the directory path itself.

Step 3: Run the generator

bash
mantle generate infra

Generated files include a header:

hcl
# 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:

hcl
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:

ModulePurpose
modules/coreNaming prefix, common tags, IAM assume-role policies, X-Ray policy
modules/lambdaLambda function, IAM role, log group, optional triggers
modules/api-gatewayREST API with stages, authorizer, usage plans
modules/queueSQS queue + DLQ + CloudWatch alarm
modules/storageS3 bucket with encryption, versioning, optional CloudFront
modules/idempotencyDynamoDB table for Lambda Powertools idempotency
modules/observabilityCloudWatch dashboards, alarms, SNS topics
modules/eventbridgeEventBridge custom event bus
modules/dynamodbDynamoDB table with GSIs, TTL, PITR
modules/websocket-apiWebSocket API Gateway
modules/database/aurora-dsqlAurora 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:

  1. Wires standard env vars (API_BEARER_TOKEN, DSQL_ENDPOINT, etc.) from project config
  2. Creates Terraform variables for non-standard env vars
  3. Resolves *_BUCKET and *_FUNCTION_NAME patterns to module outputs instead of variables
hcl
# 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:

typescript
import { defineLambda } from '@mantleframework/core'

// Standalone handlers only
defineLambda({
  timeout: 60,
  memorySize: 256,
  deadLetterQueue: true,
  retryAttempts: 2,
})
OptionTypeDefaultDescription
timeoutnumberTimeout in seconds
memorySizenumber128Memory in MB
reservedConcurrencynumberReserved concurrent executions
ephemeralStoragenumber512Ephemeral storage in MB
deadLetterQueuebooleantrue = auto-generate SQS DLQ
retryAttemptsnumberMax 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