These packages represent different approaches to building server-side JavaScript applications, from traditional Node.js frameworks to modern edge computing solutions. express, hapi, koa, micro, and polka are Node.js web frameworks with varying levels of abstraction and opinionation. @vercel/edge and @vercel/node are Vercel-specific runtime adapters for deploying functions to edge networks or Node.js environments. serverless-http acts as a bridge between traditional Node.js frameworks and serverless platforms like AWS Lambda. Each serves different deployment targets and architectural needs.
Building server-side JavaScript applications today means choosing between traditional Node.js frameworks, modern edge computing solutions, and serverless adapters. Each package in this comparison serves different deployment targets and architectural needs. Let's examine how they handle real-world development scenarios.
The packages fall into two distinct categories. express, hapi, koa, micro, and polka are full web frameworks that run on Node.js servers. @vercel/edge, @vercel/node, and serverless-http are runtime adapters that help existing code run on specific platforms.
express uses a request-response middleware pattern where each middleware can modify the request or response before passing control to the next.
// express: Middleware chain
import express from 'express';
const app = express();
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
app.get('/user/:id', (req, res) => {
res.json({ id: req.params.id });
});
app.listen(3000);
hapi uses a configuration-driven approach with plugins and built-in validation.
// hapi: Plugin-based architecture
import { Hapi } from '@hapi/hapi';
const server = Hapi.server({ port: 3000 });
server.route({
method: 'GET',
path: '/user/{id}',
handler: (request, h) => {
return { id: request.params.id };
}
});
await server.start();
koa uses async functions and middleware composition without callbacks.
// koa: Async middleware composition
import Koa from 'koa';
const app = new Koa();
app.use(async (ctx, next) => {
console.log('Time:', Date.now());
await next();
});
app.use(async ctx => {
ctx.body = { id: ctx.params.id };
});
app.listen(3000);
micro treats each export as a single request handler (now archived β see deprecation note below).
// micro: Single function per endpoint
import { send, json } from 'micro';
export default async (req, res) => {
const data = await someAsyncOperation();
send(res, 200, json({ data }));
};
polka offers Express-like routing with minimal overhead.
// polka: Lightweight routing
import polka from 'polka';
const app = polka();
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
app.get('/user/:id', (req, res) => {
res.end(JSON.stringify({ id: req.params.id }));
});
app.listen(3000);
@vercel/edge runs code on Vercel's edge network using Web Standards API.
// @vercel/edge: Edge function
import { EdgeRequest } from '@vercel/edge';
export const config = { runtime: 'edge' };
export default async function handler(request) {
const url = new URL(request.url);
return new Response(
JSON.stringify({ path: url.pathname }),
{ headers: { 'content-type': 'application/json' } }
);
}
@vercel/node runs Node.js code in Vercel's serverless environment.
// @vercel/node: Serverless Node.js function
export default async function handler(request, response) {
response.status(200).json({
path: request.url,
runtime: 'nodejs'
});
}
serverless-http wraps existing frameworks for serverless platforms.
// serverless-http: Framework adapter for Lambda
import serverless from 'serverless-http';
import express from 'express';
const app = express();
app.get('/user/:id', (req, res) => {
res.json({ id: req.params.id });
});
export const handler = serverless(app);
// Deploy handler to AWS Lambda
Bundle size matters for serverless functions where cold start time impacts user experience.
express includes many features by default, resulting in larger bundles.
// express: Full-featured but larger
import express from 'express';
// Includes body parsing, routing, middleware system
const app = express();
app.use(express.json()); // Built-in body parser
polka keeps the core minimal, requiring explicit middleware additions.
// polka: Minimal core
import polka from 'polka';
import bodyParser from 'body-parser';
const app = polka();
app.use(bodyParser.json()); // Explicit middleware
@vercel/edge has the smallest footprint but limited API surface.
// @vercel/edge: Web Standards only
export default async function handler(request) {
// No Node.js APIs like fs, path, etc.
// Only Web APIs: fetch, URL, Headers, Response
return new Response('Hello');
}
koa ships with minimal core, requiring middleware for common tasks.
// koa: Minimal core
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
const app = new Koa();
app.use(bodyParser()); // External middleware needed
Each framework handles middleware differently, affecting code organization and debugging.
express passes control with next() callback.
// express: Callback-based middleware
app.use((req, res, next) => {
if (!req.headers.auth) {
return res.status(401).send('Unauthorized');
}
next(); // Continue to next middleware
});
koa uses async/await with next() as a promise.
// koa: Async middleware
app.use(async (ctx, next) => {
if (!ctx.headers.auth) {
ctx.status = 401;
return; // No need to call next()
}
await next(); // Wait for downstream middleware
});
hapi uses lifecycle extensions with explicit phases.
// hapi: Lifecycle extensions
server.ext('onRequest', (request, h) => {
if (!request.headers.auth) {
return h.response('Unauthorized').code(401);
}
return h.continue; // Explicit continue signal
});
polka follows Express pattern with simpler implementation.
// polka: Express-like middleware
app.use((req, res, next) => {
if (!req.headers.auth) {
return res.end('Unauthorized');
}
next();
});
@vercel/edge uses standard Web API middleware patterns.
// @vercel/edge: Middleware function
export function middleware(request) {
if (!request.headers.get('auth')) {
return new Response('Unauthorized', { status: 401 });
}
// Return undefined to continue or Response to short-circuit
}
serverless-http passes through framework middleware unchanged.
// serverless-http: No middleware changes needed
// Your Express/Koa middleware works as-is
const handler = serverless(app); // app contains all middleware
Where you deploy determines which packages make sense.
@vercel/edge deploys to edge locations globally with ~100ms cold starts.
// @vercel/edge: vercel.json configuration
{
"functions": {
"api/*.js": {
"runtime": "@vercel/edge"
}
}
}
@vercel/node deploys to Vercel serverless with Node.js runtime.
// @vercel/node: vercel.json configuration
{
"functions": {
"api/*.js": {
"runtime": "@vercel/node"
}
}
}
express, koa, hapi, polka deploy to traditional Node.js servers or containers.
// Traditional deployment (Docker, VM, etc.)
import express from 'express';
const app = express();
app.listen(process.env.PORT || 3000);
// Deploy to: AWS EC2, DigitalOcean, Heroku, Docker containers
serverless-http deploys to AWS Lambda, Azure Functions, Google Cloud Functions.
// serverless-http: AWS Lambda deployment
exports.handler = serverless(app);
// Deploy via: AWS SAM, Serverless Framework, Terraform
micro was designed for Now/v1 serverless (now archived).
// micro: Historical usage (no longer recommended)
module.exports = async (req, res) => {
// Deployed to Now v1 serverless
};
Error handling varies significantly between frameworks.
express uses error-handling middleware with 4 parameters.
// express: Error middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something broke!' });
});
koa uses try/catch in async middleware.
// koa: Try/catch error handling
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = 500;
ctx.body = { error: err.message };
}
});
hapi has built-in error handling with response toolkit.
// hapi: Built-in error handling
server.route({
method: 'GET',
path: '/user',
handler: async (request, h) => {
try {
return await getUser();
} catch (err) {
return h.response(err).code(500);
}
}
});
polka uses Express-style error handling.
// polka: Error middleware
app.use((err, req, res, next) => {
res.statusCode = 500;
res.end(err.message);
});
@vercel/edge returns Response objects with error status.
// @vercel/edge: Error responses
export default async function handler(request) {
try {
const data = await fetchData();
return Response.json(data);
} catch (err) {
return Response.json({ error: err.message }, { status: 500 });
}
}
@vercel/node uses standard Node.js response objects.
// @vercel/node: Error handling
export default async function handler(request, response) {
try {
response.status(200).json({ data: 'ok' });
} catch (err) {
response.status(500).json({ error: err.message });
}
}
serverless-http passes framework errors through unchanged.
// serverless-http: Framework errors propagate
// Your Express/Koa error handlers work as-is in Lambda
const handler = serverless(app);
micro is officially archived by its maintainer. The repository shows deprecation notices and recommends alternatives.
// micro: DO NOT USE for new projects
// Archived status: https://github.com/vercel/micro
// Recommended alternatives: polka, native http module, or framework of choice
All other packages maintain active development as of current npm listings. express remains the most widely adopted with stable LTS releases. @vercel/edge and @vercel/node receive updates aligned with Vercel platform changes.
| Feature | express | hapi | koa | polka | @vercel/edge | @vercel/node | serverless-http |
|---|---|---|---|---|---|---|---|
| Runtime | Node.js | Node.js | Node.js | Node.js | Edge | Node.js | Any (adapter) |
| Middleware | Callback | Lifecycle | Async | Callback | Web API | Node.js | Pass-through |
| Bundle Size | Large | Large | Medium | Small | Minimal | Medium | +Framework |
| Learning Curve | Low | Medium | Medium | Low | Low | Low | Low |
| Serverless Ready | With adapter | With adapter | With adapter | Yes | Native | Native | Yes |
| Edge Compatible | No | No | No | No | Yes | No | No |
| Maintenance | Active | Active | Active | Active | Active | Active | Active |
Choose express when:
Choose polka when:
Choose koa when:
Choose hapi when:
Choose @vercel/edge when:
Choose @vercel/node when:
Choose serverless-http when:
Avoid micro for new projects:
The right choice depends on your deployment target and team preferences. Traditional frameworks (express, koa, hapi, polka) work anywhere Node.js runs. Platform-specific adapters (@vercel/edge, @vercel/node, serverless-http) optimize for specific hosting environments.
For new projects in 2024, consider starting with polka for APIs (small, fast, Express-like) or @vercel/edge if you're on Vercel and need edge performance. Use serverless-http only when migrating existing applications to serverless without rewriting business logic.
Remember: framework choice is less important than understanding your deployment constraints, performance requirements, and team expertise. All active packages listed here can build production-ready applications when used appropriately.
Choose @vercel/edge when deploying to Vercel's Edge Functions for low-latency, globally distributed applications. Ideal for middleware, authentication checks, and simple request/response transformations that benefit from edge caching. Not suitable for heavy computation or Node.js-specific APIs.
Choose @vercel/node when you need full Node.js compatibility within Vercel's serverless function environment. Best for existing Express/Koa applications being migrated to Vercel without major refactoring. Offers more APIs than edge but slower cold starts than edge functions.
Choose express for traditional Node.js applications requiring mature middleware ecosystem and extensive community support. Ideal for APIs, SSR applications, and teams familiar with its request/response pattern. Consider alternatives if bundle size or minimalism is a priority.
Choose hapi when building enterprise-grade APIs with strong configuration-driven architecture and built-in input validation. Suitable for teams valuing convention over flexibility and needing robust plugin systems. Less common in modern startups but stable for long-term projects.
Choose koa when you want modern async/await support with minimal framework overhead. Built by Express creators, it offers cleaner middleware composition without callback hell. Best for teams comfortable building custom abstractions on top of a lightweight core.
Choose micro for ultra-minimal HTTP microservices where each function handles a single endpoint. Now archived by maintainer β evaluate alternatives like polka or native Node.js http module for new projects. Was ideal for simple serverless functions.
Choose polka when you need Express-like routing with significantly smaller footprint and faster performance. Great for serverless functions, APIs, and projects where bundle size matters. Less middleware ecosystem than Express but growing community support.
Choose serverless-http when migrating existing Express/Koa/hapi applications to AWS Lambda or similar serverless platforms without rewriting business logic. Acts as a translation layer between Node.js frameworks and serverless event formats.
@vercel/edgeβ οΈ Deprecation Notice: This package has been unified in @vercel/functions. Use it instead!
A set of utilities to help you deploy any framework on the Edge using Vercel. Please follow the documentation for examples and usage.