A FRAMEWORK FOR AWS LAMBDAS · PYTHON / NODE / TYPESCRIPT

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
PYTHONacai-aws
1 · THE ROUTER
# 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)
2 · AN ENDPOINT
# 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
NODEacai-js
1 · THE ROUTER
// 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);
2 · AN ENDPOINT
// 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;
};
TYPESCRIPTacai-ts
1 · THE ROUTER
// 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);
2 · AN ENDPOINT
// 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;
    }
}
WIRE IT UP · serverless.yml
functions:
    api-handler:
        handler: api/handler.handle
        events:
            - http: { path: /{proxy+}, method: ANY }
PYTHONacai-aws
THE HANDLER — validated before it runs
# 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)
NODEacai-js
THE HANDLER — validated before it runs
// 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);
    }
};
TYPESCRIPTacai-ts
THE HANDLER — validated before it runs
// 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);
    }
};
WIRE IT UP · serverless.yml
functions:
    sqs-handler:
        handler: handlers/sqs.handle
        events:
            - sqs: { arn: !GetAtt GrowerQueue.Arn }
PYTHONacai-aws
THE HANDLER — streams, filtered by operation
# 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)
NODEacai-js
THE HANDLER — streams, filtered by operation
// 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);
    }
};
TYPESCRIPTacai-ts
THE HANDLER — streams, filtered by operation
// 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);
    }
};
WIRE IT UP · serverless.yml
functions:
    ddb-handler:
        handler: handlers/dynamodb.handle
        events:
            - stream: { type: dynamodb, arn: !GetAtt GrowerTable.StreamArn }
01

Auto-routing

A configurable internal router maps files to routes — convention over configuration, no route tables.

02

Schema validation

Declare required bodies, headers and OpenAPI schemas; invalid requests never reach your logic.

03

Middleware, everywhere

Extensible before/after hooks for auth, timeouts and anything else — declared, not hand-rolled.

EVERY EVENT SOURCE
API Gateway DynamoDB S3 SQS SNS Kinesis Firehose MSK MQ DocumentDB