The happy path,
in every language.
Acai is a DRY, declarative framework for AWS Lambdas. One philosophy, three runtimes — validate every input first, then write only the code that matters.
“No nested try/catch. No mid-level exceptions. Just the path your code was meant to take.”
Happy Path Programming is a simple idea: all inputs are validated before they are operated on. Acai's layers of configurable middleware let you declare what a valid input looks like — required bodies, headers, OpenAPI schemas — so invalid requests never reach your business logic.
What's left is code that reads the way you meant it: no defensive conditionals, no exception plumbing, no boilerplate. Just the happy path.
One idea, three languages
same framework · every trigger# handler.py from acai_aws.apigateway.router import Router router = Router( base_path='grower/v1', handlers='api/handlers', schema='api/openapi.yml' ) router.auto_load() def handle(event, context): return router.route(event, context)
# api/handlers/grower.py requirements = { 'post': {'required_body': 'v1-grower-request'} } def post(request, response): # already validated — happy path only response.body = {'grower': request.body} return response
// handler.js const { Router } = require('acai').apigateway; const router = new Router({ basePath: 'grower/v1', handlerPath: 'api/handlers', schemaPath: 'api/openapi.yml' }); exports.handle = async (event) => router.route(event);
// api/handlers/grower.js exports.requirements = { post: { requiredBody: 'v1-grower-request' } }; exports.post = async (request, response) => { // already validated — happy path only response.body = { grower: request.body }; return response; };
// handler.ts import { Router } from 'acai-ts'; const router = new Router({ basePath: 'grower/v1', routesPath: './src/handlers/**/*.ts', schemaPath: './openapi.yml' }); export const handler = async (event) => router.route(event);
// src/handlers/grower.ts export class GrowerEndpoint extends BaseEndpoint { @Validate({ requiredBody: 'v1-grower-request' }) async post(request: Request, response: Response) { // already validated — happy path response.body = { grower: request.body }; return response; } }
functions:
api-handler:
handler: api/handler.handle
events:
- http: { path: /{proxy+}, method: ANY }
# handlers/sqs.py from acai_aws.sqs.requirements import requirements @requirements( required_body='v1-sqs-event' ) def handle(event): for record in event.records: process(record.body)
// handlers/sqs.js const { sqs } = require('acai'); exports.handle = async (event) => { const sqsEvent = new sqs.Event(event, { requiredBody: 'v1-sqs-event', schemaPath: 'openapi.yml' }); const records = await sqsEvent.getRecords(); for (const record of records) { await process(record.body); } };
// src/handlers/sqs.ts import { Event } from 'acai-ts/sqs'; import { SQSEvent } from 'aws-lambda'; export const handle = async (event: SQSEvent) => { const sqsEvent = new Event(event, { requiredBody: 'v1-sqs-event', schemaPath: './openapi.yml' }); await sqsEvent.process(); for (const record of sqsEvent.records) { await process(record.body); } };
functions:
sqs-handler:
handler: handlers/sqs.handle
events:
- sqs: { arn: !GetAtt GrowerQueue.Arn }
# handlers/dynamodb.py from acai_aws.dynamodb.requirements import requirements @requirements( operations=['created', 'deleted'] ) def handle(event): for record in event.records: sync(record.new_image)
// handlers/dynamodb.js const { dynamodb } = require('acai'); exports.handle = async (event) => { const ddbEvent = new dynamodb.Event(event, { operations: ['create', 'delete'] }); for (const record of ddbEvent.records) { await sync(record.newImage); } };
// src/handlers/dynamodb.ts import { Event } from 'acai-ts/dynamodb'; import { DynamoDBStreamEvent } from 'aws-lambda'; export const handle = async (event: DynamoDBStreamEvent) => { const ddbEvent = new Event(event, { operations: ['create', 'delete'] }); for (const record of ddbEvent.records) { await sync(record.newImage); } };
functions:
ddb-handler:
handler: handlers/dynamodb.handle
events:
- stream: { type: dynamodb, arn: !GetAtt GrowerTable.StreamArn }
Auto-routing
A configurable internal router maps files to routes — convention over configuration, no route tables.
Schema validation
Declare required bodies, headers and OpenAPI schemas; invalid requests never reach your logic.
Middleware, everywhere
Extensible before/after hooks for auth, timeouts and anything else — declared, not hand-rolled.