express vs koa vs @vercel/node vs micro vs polka vs @vercel/edge vs serverless-http vs hapi
Server-Side JavaScript Runtimes and Frameworks for Modern Web Applications
expresskoa@vercel/nodemicropolka@vercel/edgeserverless-httphapiSimilar Packages:

Server-Side JavaScript Runtimes and Frameworks for Modern Web Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
express128,585,31069,31475.4 kB2258 months agoMIT
koa7,855,42235,69365 kB422 months agoMIT
@vercel/node3,919,09516,0202.97 MB808a day agoApache-2.0
micro3,386,08810,62242.1 kB8-MIT
polka1,050,0845,587-317 years agoMIT
@vercel/edge731,21516,02048.6 kB808a day agoApache-2.0
serverless-http485,4531,77939.7 kB59a year agoMIT
hapi80,89614,793-567 years agoBSD-3-Clause

Server-Side JavaScript Runtimes and Frameworks: A Technical Deep-Dive

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.

πŸ—οΈ Core Architecture: Framework vs Runtime Adapter

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

⚑ Performance and Bundle Size Considerations

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

πŸ”„ Middleware and Request Handling Patterns

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

🌍 Deployment Targets and Runtime Constraints

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 and Response Management

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);

⚠️ Deprecation and Maintenance Status

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 Comparison Summary

Featureexpresshapikoapolka@vercel/edge@vercel/nodeserverless-http
RuntimeNode.jsNode.jsNode.jsNode.jsEdgeNode.jsAny (adapter)
MiddlewareCallbackLifecycleAsyncCallbackWeb APINode.jsPass-through
Bundle SizeLargeLargeMediumSmallMinimalMedium+Framework
Learning CurveLowMediumMediumLowLowLowLow
Serverless ReadyWith adapterWith adapterWith adapterYesNativeNativeYes
Edge CompatibleNoNoNoNoYesNoNo
MaintenanceActiveActiveActiveActiveActiveActiveActive

πŸ’‘ Practical Selection Guide

Choose express when:

  • You need the largest middleware ecosystem
  • Your team has Express experience
  • You're deploying to traditional servers or containers
  • Bundle size isn't a critical concern

Choose polka when:

  • You want Express-like DX with smaller footprint
  • You're building serverless functions
  • Performance and cold starts matter
  • You prefer minimal framework overhead

Choose koa when:

  • You want modern async/await patterns
  • You're comfortable building custom abstractions
  • You need cleaner middleware composition than Express
  • Your team values code clarity over convention

Choose hapi when:

  • You need enterprise-grade configuration
  • Built-in validation is important
  • You prefer convention over flexibility
  • Long-term stability matters more than trends

Choose @vercel/edge when:

  • You deploy to Vercel
  • Low latency is critical (global users)
  • Your logic is simple (auth, redirects, headers)
  • You can work within Web Standards API limits

Choose @vercel/node when:

  • You deploy to Vercel
  • You need full Node.js API access
  • You're migrating existing Express/Koa apps
  • Cold start time is acceptable for your use case

Choose serverless-http when:

  • You deploy to AWS Lambda or similar
  • You have existing Express/Koa/hapi code
  • You want minimal refactoring for serverless
  • You need framework features in serverless environment

Avoid micro for new projects:

  • Officially archived by maintainer
  • Limited to single-function endpoints
  • Better alternatives exist (polka, native http)
  • No active maintenance or security updates

🎯 Final Thoughts

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.

How to Choose: express vs koa vs @vercel/node vs micro vs polka vs @vercel/edge vs serverless-http vs hapi

  • express:

    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.

  • koa:

    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.

  • @vercel/node:

    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.

  • micro:

    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.

  • polka:

    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.

  • @vercel/edge:

    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.

  • serverless-http:

    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.

  • hapi:

    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.

README for express

Express Logo

Fast, unopinionated, minimalist web framework for Node.js.

This project has a Code of Conduct.

Table of contents

NPM Version NPM Downloads Linux Build Test Coverage OpenSSF Scorecard Badge

import express from 'express'

const app = express()

app.get('/', (req, res) => {
  res.send('Hello World')
})

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000')
})

Installation

This is a Node.js module available through the npm registry.

Before installing, download and install Node.js. Node.js 18 or higher is required.

If this is a brand new project, make sure to create a package.json first with the npm init command.

Installation is done using the npm install command:

npm install express

Follow our installing guide for more information.

Features

  • Robust routing
  • Focus on high performance
  • Super-high test coverage
  • HTTP helpers (redirection, caching, etc)
  • View system supporting 14+ template engines
  • Content negotiation
  • Executable for generating applications quickly

Docs & Community

PROTIP Be sure to read the migration guide to v5

Quick Start

The quickest way to get started with express is to utilize the executable express(1) to generate an application as shown below:

Install the executable. The executable's major version will match Express's:

npm install -g express-generator@4

Create the app:

express /tmp/foo && cd /tmp/foo

Install dependencies:

npm install

Start the server:

npm start

View the website at: http://localhost:3000

Philosophy

The Express philosophy is to provide small, robust tooling for HTTP servers, making it a great solution for single page applications, websites, hybrids, or public HTTP APIs.

Express does not force you to use any specific ORM or template engine. With support for over 14 template engines via @ladjs/consolidate, you can quickly craft your perfect framework.

Examples

To view the examples, clone the Express repository:

git clone https://github.com/expressjs/express.git --depth 1 && cd express

Then install the dependencies:

npm install

Then run whichever example you want:

node examples/content-negotiation

Contributing

The Express.js project welcomes all constructive contributions. Contributions take many forms, from code for bug fixes and enhancements, to additions and fixes to documentation, additional tests, triaging incoming pull requests and issues, and more!

See the Contributing Guide for more technical details on contributing.

Security Issues

If you discover a security vulnerability in Express, please see Security Policies and Procedures.

Running Tests

To run the test suite, first install the dependencies:

npm install

Then run npm test:

npm test

Current project team members

For information about the governance of the express.js project, see GOVERNANCE.md.

The original author of Express is TJ Holowaychuk

List of all contributors

TC (Technical Committee)

TC emeriti members

TC emeriti members

Triagers

Triagers emeriti members

Emeritus Triagers

License

MIT