RepoPilot

How do their APIs compare?

About expressjs/express · 2 messages · last activity 8/2/2026

Question

How do their APIs compare?

RepoPilot

Here's a comparison of the core API styles of expressjs/express and hapijs/hapi, drawing on the retrieved code and docs.

Application/Server creation & routing style

Express builds an app with a chainable, minimalist API centered on app.use() for middleware and app.get/post/delete/etc() for routes, with handlers taking (req, res, next). Routing is expressed as ordered middleware/handler chains. For example, the web-service example mounts a validation middleware on a path prefix and follows it with route handlers:

app.use('/api', function(req, res, next){ ... });
app.get('/api/users', function (req, res) { res.send(users); });

[examples/web-service/index.js:L28-L38,L69-L72]

Error handling is done via a special 4-arity middleware signature (err, req, res, next), and a final catch-all middleware for 404s — both relying on middleware order rather than declarative config: [examples/web-service/index.js:L92-L104]

Route-specific auth/authorization in Express is just composed middleware functions passed positionally into the route definition, e.g. loadUser, andRestrictToSelf, andRestrictTo('admin'): [examples/route-middleware/index.js:L50-L81]

Hapi takes a more configuration-object-driven approach. Routes are defined via server.route({ method, path, options: { handler, auth } }), where cross-cutting concerns like authentication and scope are declared as structured config rather than middleware chains:

server.route({
    method: 'GET',
    path: '/',
    options: {
        handler: (request) => request.auth.credentials.user,
        auth: { scope: ['!a', 'b'] }
    }
});

[test/auth.js:L972-L982]

Handlers in hapi receive (request, h) and typically return the response value directly (async/await friendly) rather than calling res.send().

Authentication model

  • Express has no built-in auth concept — the examples/auth and examples/route-middleware examples show auth implemented entirely by hand as ordinary middleware functions (e.g., a custom authenticate(name, pass, fn) callback checking a hash) [examples/auth/index.js:L60-L74], or an API-key check middleware mounted on a path [examples/web-service/index.js:L28-L38]. There's no schema for "strategies."

  • Hapi has first-class, pluggable authentication built into the framework: server.auth.scheme() defines a scheme, server.auth.strategy() registers a named strategy, and server.auth.default() sets a default. Strategies can expose a custom API via server.auth.api [API.md:L360-L380], and there's a dedicated test utility server.auth.test(strategy, request) to validate credentials without going through full route dispatch [API.md:L1078-L1115]. Scope-based authorization (e.g. scope: ['!a', 'b']) is declarative per-route config, verified in [test/auth.js:L953-L989].

Extensibility model

  • Express extends behavior mainly through middleware functions and prototype methods on req/res (e.g. res.links() for building Link headers) [lib/response.js:L97-L109]. There's no formal decoration/plugin system — extension is just adding properties/functions to req/res objects or composing middleware.

  • Hapi has an explicit server.decorate() API to attach custom properties/methods to request, server, h, etc., with per-server isolation (decorations don't leak between separate Hapi.server() instances) as shown in [test/request.js:L41-L68,L110-L118]. This is a more structured, discoverable extension mechanism compared to Express's ad hoc prototype patching.

Configuration philosophy

  • Express has few built-in options; app behavior is assembled from middleware you choose to use(). Even things like content negotiation or JSON APIs are shown as example patterns rather than framework features (see the content-negotiation comment in [examples/web-service/index.js:L18-L19]).

  • Hapi exposes a rich, documented set of server-level options (server.options.query.parser, server.options.router, server.options.operations, server.options.plugins, server.options.port) that govern query parsing, routing case-sensitivity/trailing slashes, connection cleanup on stop, plugin config, and port

Want to ask your own question?

Open chat for expressjs/express

Public Q&A. Generated by RepoPilot from the actual source of expressjs/express. AI answers can be incomplete or stale — verify before relying on them.