The Problem: Serverless Lock-In Is Real
You started with AWS Lambda even used a framework like Serverless.com. It made sense—no servers to manage, pay-per-use pricing, and quick deployments. Life was good.
Then you tried to debug something locally.
The reality is that local development with Lambda is painful. You're constantly dealing with mocked API Gateway events, environment differences between local and deployed code, and debugging workflows that don't match how you'd normally work with Node.js. When something breaks in production, reproducing it locally means wrestling with event structures and Lambda-specific context objects.
And if your team ever decides to move to Docker or Kubernetes? Your entire codebase is tied to Lambda's event structure—migration means rewriting your API layer.
The Solution: Write Once, Deploy Anywhere
What if you could write your API handlers once and deploy them to any runtime without changing a single line of business logic?
That's exactly what FMiddleware does.
FMiddleware is a framework-agnostic HTTP middleware library that abstracts away the runtime differences between AWS Lambda and Express.js. Your handlers stay the same—only the deployment wrapper changes.
Quick Start: See It In Action
Install the package:
npm install @loupeat/fmiddleware
Define your API handler once:
import { FRequest } from "@loupeat/fmiddleware";
interface Note {
id: string;
title: string;
}
// This handler works on BOTH Lambda and Express
api.get("/api/notes", async (request: FRequest<any, any>) => {
const notes: Note[] = [{ id: "1", title: "Hello World" }];
return api.responses.OK<any, Note[]>(request, notes);
});
Deploy to Express.js (Local Dev / Docker)
import express from "express";
import { FExpressMiddleware, FRequest } from "@loupeat/fmiddleware";
const app = express();
const api = new FExpressMiddleware();
// Register your routes (same as above)
api.get("/api/notes", async (request: FRequest<any, any>) => {
const notes = [{ id: "1", title: "Hello World" }];
return api.responses.OK(request, notes);
});
// Use FMiddleware as Express middleware
app.use(express.json());
app.all("*", async (req, res) => {
const response = await api.process(req);
res.status(response.statusCode).json(response.body);
});
app.listen(3000);
Deploy to AWS Lambda (Production)
import { APIGatewayProxyHandler } from "aws-lambda";
import { FAWSLambdaMiddleware, FRequest } from "@loupeat/fmiddleware";
const api = new FAWSLambdaMiddleware();
// Same exact handler code!
api.get("/api/notes", async (request: FRequest<any, any>) => {
const notes = [{ id: "1", title: "Hello World" }];
return api.responses.OK(request, notes);
});
export const handler: APIGatewayProxyHandler = async (event) => {
return api.process(event);
};
Notice something? The handler code is identical. Only the wrapper changes.
Why This Matters for Developer Productivity
1. Seamless Local Development
No more mocking Lambda events or fighting with local emulators. Just run your Express server locally and test like any normal Node.js app:
npm run dev # Uses Express wrapper
When you're ready for production, deploy with your Lambda wrapper—zero code changes.
2. Easy Migration Path
Already on Lambda and want to move to Docker? Simply:
- Swap
FAWSLambdaMiddlewareforFExpressMiddleware - Add a Dockerfile
- Deploy
Your handlers, validation, authentication—everything stays the same.
3. Hybrid Deployments
Running some endpoints on Lambda for cost efficiency and others on ECS for performance? FMiddleware lets you share the exact same handler code across both.
Batteries Included: Features That Actually Help
FMiddleware isn't just a wrapper—it's a full-featured API toolkit:
TypeScript-First
Full type safety for requests and responses:
interface CreateNoteRequest {
title: string;
content: string;
}
api.post<any, CreateNoteRequest>("/api/notes", async (request) => {
const { title, content } = request.body; // Fully typed!
// ...
});
Built-in JSON Schema Validation
const CreateNoteSchema = {
type: "object",
properties: {
title: { type: "string", minLength: 1 },
content: { type: "string" },
},
required: ["title", "content"]
};
api.post("/api/notes", async (request) => {
// request.body is automatically validated
const { title, content } = request.body;
// ...
}, CreateNoteSchema);
Pre-Processors for Authentication
const AuthPreProcessor: RequestPreProcessor = {
name: "AuthPreProcessor",
pathPatterns: ["/api/notes/*"],
requestSources: "*", // Works on both Express and Lambda
process: async ({ request }) => {
const authHeader = request.headers["authorization"];
if (!authHeader) {
throw new AuthenticationError("Missing authorization header");
}
const token = authHeader.replace(/Bearer /, "");
const user = await authService.verifyToken(token);
request.context["user"] = user;
}
};
api.addRequestPreProcessor(AuthPreProcessor);
Semantic Error Classes
import {
ValidationError, // 400 Bad Request
AuthenticationError, // 401 Unauthorized
ForbiddenError, // 403 Forbidden
NotFoundError, // 404 Not Found
ConflictError, // 409 Conflict
} from "@loupeat/fmiddleware";
// Errors automatically map to correct HTTP status codes
if (!note) {
throw new NotFoundError(`Note ${noteId} not found`);
}
OpenAPI Generation
Automatically generate OpenAPI 3.0 specs from your handlers:
import { FExpressMiddleware, OpenAPIMetadata } from "@loupeat/fmiddleware";
api.get("/api/notes", async (request) => {
const notes = await notesService.list();
return api.responses.OK(request, notes);
}, {
summary: "List all notes",
description: "Retrieves all notes for the authenticated user",
tags: ["Notes"],
});
Real-World Deployment
FMiddleware works with any deployment tool—Serverless Framework, AWS CDK, SST, or plain CloudFormation. Here's an example using Serverless Framework:
service: notes-api
plugins:
- serverless-esbuild
provider:
name: aws
runtime: nodejs20.x
region: eu-west-1
functions:
api:
handler: src/handler.main
events:
- http:
method: any
path: "api/{proxy+}"
cors: true
timeout: 15
When to Use FMiddleware
FMiddleware is perfect if you:
- Want flexibility to switch between serverless and containerized deployments
- Value local development experience that mirrors production
- Need a migration path away from Lambda without rewriting everything
- Want type-safe APIs with built-in validation
- Prefer framework-agnostic code that doesn't lock you in
Get Started Today
npm install @loupeat/fmiddleware
# For AWS Lambda support
npm install --save-dev @types/aws-lambda
Check out the full documentation and examples:
- GitHub: loupeat/fmiddleware
- npm: @loupeat/fmiddleware
Have you dealt with serverless lock-in? How did you handle the migration? I'd love to hear your experiences in the comments!
FMiddleware is open source and MIT licensed. Contributions welcome!
Top comments (0)