# Web Frameworks

A backend web framework organizes the path from an HTTP request to a response. Node's HTTP API provides the transport; frameworks add conventions for middleware, routing, errors and response handling. They do not replace HTTP, and they are different from frontend UI frameworks or build tools such as Webpack.

Framework Main idea Useful when
Express (opens new window) Minimal routing and middleware with a large ecosystem You want a small server with explicit routes
Koa (opens new window) Async middleware around a shared request context You want to understand and control the request pipeline
NestJS (opens new window) Modules, controllers and dependency injection A larger application benefits from a shared architecture

# A request through Koa

This standalone server.cjs example uses Koa's async middleware API, available in Koa 2 and 3. It assumes koa is installed in the example application's dependencies. Run it with node server.cjs and request http://localhost:3000/api/students.

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    ctx.status = 500;
    ctx.body = { error: 'Internal server error' };
    ctx.app.emit('error', error, ctx);
  }
});

app.use(async (ctx, next) => {
  const started = Date.now();
  console.log('before handler');
  await next();
  ctx.set('X-Response-Time', `${Date.now() - started}ms`);
  console.log('after handler');
});

app.use(ctx => {
  if (ctx.method !== 'GET' || ctx.path !== '/api/students') return;
  console.log('handler');
  ctx.body = [{ id: 1, name: 'Ada' }];
});

app.listen(3000, '127.0.0.1');

ctx is the context for one request: method and path identify the operation, ctx.body supplies the response, and ctx.status can set its status code. Koa serializes the array as JSON and normally sets status 200 when a body is supplied. An unmatched request remains a 404 in this example.

# Middleware runs downstream and back upstream

app.use registers middleware in order. Calling await next() transfers control to the next middleware and waits for it before continuing:

request → error boundary → timing middleware → handler
response ← error boundary ← timing middleware ← handler

before handler
handler
after handler

This is often called the onion model. With two wrappers A and B, the sequence is A before → B before → handler → B after → A after. Timing, logging and error handling can therefore surround the handler rather than being repeated in every route.

  • Omitting next() short-circuits the pipeline; later middleware will not run. This is useful for a completed response or a rejected request.
  • Calling next() without await or returning its Promise does not wait for downstream work; timing and error handling can then be incorrect.
  • Put the error boundary before middleware whose failures it must catch. Log diagnostics on the server; do not send stack traces to clients.

Authentication is another middleware responsibility, but a hardcoded secret in a query string is not a production authentication scheme: URLs can be recorded in logs and history. It is deliberately not part of this minimal server.

# API responses and frontend files

An API route returns data; a static-file handler serves assets such as HTML, CSS and the generated JavaScript bundle. Koa itself does not include a router, body parser or static-file server: these require explicit middleware or a separate server. Use a maintained static-serving solution with a fixed asset directory, not unrestricted reads of request-supplied filesystem paths.

A development proxy can let the frontend call this API during development. Production needs an actual API server and an explicit way to serve the built assets; Webpack is not that backend.

Check your understanding: what happens if the handler never calls next()? Why can the timing middleware still execute its final statements?

Continue with TypeScript. Full API reference: Koa documentation (opens new window).