All posts
zapixserverlessaws-lambdaopen-source

Why We Built Zapix: Express-style Routing for AWS Lambda

The story behind Zapix — how the frustration of writing boilerplate Lambda handlers led us to build a tiny, zero-config routing library with the DX you already love.

Raihan Sharif Rimon·March 14, 2026·5 min read

Every time I started a new AWS Lambda project, the routine was the same: write a giant switch-case on event.httpMethod + event.path, manually parse JSON bodies, forget to set Content-Type headers, reinvent 404 handling, and paste the same middleware boilerplate from the last project. Sound familiar?

Zapix was born out of that frustration. The goal was simple: bring the Express.js developer experience to serverless Lambda — without sacrificing cold-start performance or adding a mountain of dependencies.

The problem with Lambda handlers in the wild

Most Lambda HTTP handlers end up looking something like this:

handler.ts (before Zapix)
export async function handler(event: APIGatewayProxyEvent) {
  if (event.httpMethod === 'GET' && event.path === '/users') {
    const users = await getUsers();
    return {
      statusCode: 200,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(users),
    };
  }
  if (event.httpMethod === 'POST' && event.path === '/users') {
    const body = JSON.parse(event.body ?? '{}');
    // validate, save, respond...
  }
  return { statusCode: 404, body: 'Not found' };
}

This is brittle, hard to test, and impossible to scale. Add a few more routes and you have a 200-line function nobody wants to touch.

The Zapix approach

We took inspiration from Express.js — arguably the most ergonomic HTTP routing API ever designed — and built a minimal adapter that speaks the Lambda event/response protocol natively:

handler.ts (with Zapix)
import { Zapix } from 'zapix';
import { handler } from 'zapix/aws';

const app = Zapix();

app.get('/users', async (req, res) => {
  const users = await getUsers();
  res.json(users);
});

app.post('/users', async (req, res) => {
  const body = req.body;
  const user = await createUser(body);
  res.status(201).json(user);
});

export const main = handler(app);
💡 Tip:The handler() adapter converts the Zapix app into a standard Lambda handler. You export main and Lambda calls it — no glue code needed.

Design decisions we made deliberately

  • Zero dependencies — No bloated node_modules subtree slowing down your cold start. Zapix has zero runtime dependencies.
  • < 5KB bundle — Small enough to fit comfortably in a Lambda layer or inline in any deployment package.
  • TypeScript first — Full type inference on req, res, and route params. No @types/* packages needed.
  • Zero config — Import, route, export. Done. No app.listen(), no port management, no server lifecycle.
  • Chainable middleware — app.use() works exactly the way you expect it to.

What's next

We just released v1.0.0-beta.1. The API is stable and we're using it in production. Coming up: route-level caching hints, built-in validation helpers, and a companion CDK/SAM construct for instant deployment.

If Zapix solves a real problem for you, give us a star on GitHub — it helps more developers find the project.

Try Zapix in your next Lambda project

Zero config. TypeScript first. Under 5KB.