NextRush
Performance

Framework Comparison

Side-by-side code comparison of NextRush, Express, Fastify, Hono, and Koa — routing, middleware, error handling, and TypeScript support.

How NextRush compares to popular Node.js frameworks — not only speed, but how you write code.

Looking for benchmarks?

This page compares code patterns and architecture. For raw performance numbers, see Performance.

At a Glance

FeatureNextRushExpressFastifyHonoKoa
TypeScriptFirst-class (strict)Bolt-on (@types)Built-inBuilt-inBolt-on (@types)
Runtime deps03115024
Multi-runtimeNode, Bun, Deno, EdgeNode onlyNode onlyNode, Bun, Deno, EdgeNode only
Context APISingle ctx objectreq + res + nextrequest + replySingle c objectSingle ctx object
Middleware modelKoa-style onionLinear chainHooks + lifecycleKoa-style onionOnion model
Built-in DIYes (@nextrush/di)NoNoNoNo
DecoratorsYes (@Controller, @Get)NoVia pluginsNoNo
Plugin systemPlugin interfaceapp.use()Encapsulated pluginsMiddleware onlyapp.use()
RoutingRadix tree (O(k))Linear scan (O(n))Radix tree (O(k))Radix tree (O(k))Via koa-router

Hello World

The same endpoint in every framework.

src/index.ts
import { createApp, createRouter, listen } from 'nextrush';

const app = createApp();
const router = createRouter();

router.get('/', (ctx) => ctx.json({ message: 'Hello World' }));
app.route('/', router);
await listen(app, 3000);
src/index.ts
import express from 'express';

const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Hello World' });
});

app.listen(3000);
src/index.ts
import Fastify from 'fastify';

const app = Fastify();

app.get('/', async () => {
  return { message: 'Hello World' };
});

await app.listen({ port: 3000 });
src/index.ts
import { Hono } from 'hono';
import { serve } from '@hono/node-server';

const app = new Hono();

app.get('/', (c) => c.json({ message: 'Hello World' }));

serve({ fetch: app.fetch, port: 3000 });
src/index.ts
import Koa from 'koa';
import Router from 'koa-router';

const app = new Koa();
const router = new Router();

router.get('/', (ctx) => {
  ctx.body = { message: 'Hello World' };
});

app.use(router.routes());
app.listen(3000);

Middleware

How each framework composes middleware — logging, auth, and error handling.

Koa-style onion model. Code before ctx.next() runs on the way in, code after runs on the way out.

// Timing middleware
app.use(async (ctx) => {
  const start = Date.now();
  await ctx.next();
  ctx.set('X-Response-Time', `${Date.now() - start}ms`);
});

// Auth middleware
app.use(async (ctx) => {
  const token = ctx.get('authorization');
  if (!token) throw new UnauthorizedError('Missing token');
  ctx.state.user = verifyToken(token);
  await ctx.next();
});

Linear chain with next() callback. Error handling is a separate signature.

// Timing middleware
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    res.set('X-Response-Time', `${Date.now() - start}ms`);
  });
  next();
});

// Auth middleware
app.use((req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).json({ error: 'Missing token' });
  req.user = verifyToken(token);
  next();
});

// Error middleware (must have 4 args)
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message });
});

Hook-based lifecycle instead of middleware chain.

// Timing via hooks
app.addHook('onRequest', async (request, reply) => {
  request.startTime = Date.now();
});
app.addHook('onSend', async (request, reply) => {
  reply.header('X-Response-Time', `${Date.now() - request.startTime}ms`);
});

// Auth via hook
app.addHook('onRequest', async (request, reply) => {
  const token = request.headers.authorization;
  if (!token) {
    reply.code(401).send({ error: 'Missing token' });
    return;
  }
  request.user = verifyToken(token);
});

Onion model — same pattern as NextRush.

// Timing middleware
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  ctx.set('X-Response-Time', `${Date.now() - start}ms`);
});

// Auth middleware
app.use(async (ctx, next) => {
  const token = ctx.get('authorization');
  if (!token) ctx.throw(401, 'Missing token');
  ctx.state.user = verifyToken(token);
  await next();
});

Key difference: NextRush and Koa share the onion model — you write both "before" and "after" logic in one function. Express uses linear chains with separate error middleware. Fastify uses lifecycle hooks.


Error Handling

Typed error classes with automatic status codes. Thrown errors become JSON responses.

import { NotFoundError, ValidationError } from '@nextrush/errors';

router.get('/users/:id', (ctx) => {
  const user = db.find(ctx.params.id);
  if (!user) throw new NotFoundError('User not found');
  ctx.json(user);
});

// Custom error handler
app.setErrorHandler((error, ctx) => {
  ctx.status = error.status ?? 500;
  ctx.json({
    error: error.message,
    code: error.code,
  });
});

Manual status codes. Error middleware requires 4 parameters.

app.get('/users/:id', (req, res, next) => {
  const user = db.find(req.params.id);
  if (!user) {
    const err = new Error('User not found');
    err.status = 404;
    return next(err);
  }
  res.json(user);
});

// Error middleware (must be last, must have 4 args)
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message });
});

Schema-based error serialization. Custom error handler per route or global.

app.get('/users/:id', async (request, reply) => {
  const user = db.find(request.params.id);
  if (!user) {
    reply.code(404);
    return { error: 'User not found' };
  }
  return user;
});

app.setErrorHandler(async (error, request, reply) => {
  reply.status(error.statusCode || 500).send({
    error: error.message,
  });
});

TypeScript Support

AspectNextRushExpressFastify
Source languageTypeScriptJavaScriptTypeScript
Types includedYes (strict mode)No — install @types/expressYes
Route param inferenceVia generics on ctxManual casting requiredVia JSON Schema
Body type safetyTransform decorators (Zod, etc.)None built-inJSON Schema validation
Strict modeEnforced — zero anyOptionalOptional
// NextRush — types flow naturally
router.get('/users/:id', (ctx) => {
  const id = ctx.params.id; // string (typed)
  const page = ctx.query.page; // string | undefined (typed)
  ctx.json({ id, page }); // return type checked
});

When to Choose What

Your priorityLikely fitNotes
Raw throughput (Node.js only)FastifyAOT JSON serialization, mature radix tree
Multi-runtime (Bun, Deno, Edge)NextRush or HonoCompare adapters and deploy targets
Strict TypeScript + small core depsNextRushCore packages avoid extra runtime dependencies
Large ecosystem + communityExpressBroad middleware ecosystem and tutorials
Class-based + DINextRushOptional decorators and DI in the same stack
Minimal learning curveKoa or ExpressFamiliar patterns and documentation
Mixed functional + class routesNextRushBoth styles can coexist in one app

Migration Paths

Ready to switch? Follow the step-by-step guides:

On this page