codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
Notes›backend

API handler pattern

Published 2026-04-28· Updated 2026-05-18·0 views

API handler pattern and cross-cutting concerns

When writing an HTTP API, the code around the handler body (auth, validation, logging, error translation, rate limiting) often outweighs the body itself. Where and how this cross-cutting code lives decides consistency and maintenance cost.

1. Per-framework mechanisms

The mechanisms for expressing cross-cutting concerns vary in name across frameworks.

Framework Mechanism Form
Express (Node) middleware (req, res, next) => ...
Koa middleware async (ctx, next) => ...
Spring MVC HandlerInterceptor · Filter Class + annotation
FastAPI Depends · middleware Function dependency + ASGI middleware
NestJS Interceptor · Guard · Pipe Decorator + class
Next.js Route Handlers Wrapper function withAuth(handler) style composition
Hono middleware app.use(...)

One standard response format is RFC 7807 — Problem Details for HTTP APIs (2016, later revised as RFC 9457). It defines the application/problem+json content type and the type · title · status · detail · instance fields.

2. Middleware chain (Express model)

A request passes through middlewares in the order they were registered, and each calls next() to advance. Sending a response directly or throwing an error breaks the chain there.

app.use(requestId)
app.use(logger)
app.use(authenticate)
app.use('/admin', authorize('admin'))
app.use(routes)
app.use(errorHandler)  // (err, req, res, next) — error-only

Koa re-expressed the same model in async/await. It is called the "onion model" because the same middleware can wake up after the response to do final processing.

3. Spring's Interceptor and Filter

A Servlet Filter sits at the outermost layer; HandlerInterceptor sits closer to the controller. @RestControllerAdvice translates exceptions in one place.

@RestControllerAdvice
class GlobalExceptionHandler {
  @ExceptionHandler(NotFoundException.class)
  ResponseEntity<ProblemDetail> notFound(NotFoundException e) {
    var p = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
    return ResponseEntity.of(p).build();
  }
}

From Spring 6, the ProblemDetail class produces RFC 7807 responses in a standardized way.

4. FastAPI's Depends + middleware

Depends is evaluated right before the handler enters and carries auth, DB session, and validation. app.add_middleware carries ASGI-level global processing. Beyond HTTPException, FastAPI can translate user-defined exceptions via app.exception_handler.

5. Next.js wrapper

Next.js App Router's route.ts is a function. Cross-cutting concerns are added by composition.

export const GET = withAuth(withLogging(async (req) => {
  return NextResponse.json({ ok: true })
}))

Function composition does not use decorators or classes — it stays light, but order and types must be managed by hand.

6. RFC 7807 response

{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "status": 403,
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc"
}

The strength is that an API client can assume standard fields when parsing the response. Adding custom fields like errors[] is also allowed by the spec.

7. Function composition vs class decorators

  • Function composition (Express · Hono · Next wrapper) — the flow reads top-to-bottom. Dependencies are explicit. Indentation grows as composition deepens.
  • Class decorators (NestJS · Spring) — declared via metadata. Handlers stay short. Annotation order and priority can be implicit.
  • Dependency injection (FastAPI Depends) — dependencies are written in the signature. A middle ground between composition and decorators.

Whichever form, the goal is the same — cross-cutting concerns gather in one place and the handler focuses on the body.

8. ApiError standard response class

Having a custom ApiError class is a common pattern.

class ApiError extends Error {
  constructor(public status: number, public code: string, public detail?: string) { super(detail) }
}

When thrown, a global handler translates it into problem+json. Mapping RFC 7807's type to a domain code lets clients branch on it.

9. Middleware order

The following order is generally safe.

① Request ID assignment (observability)
② Security headers (Helmet-style)
③ CORS
④ Logging
⑤ Body parsing
⑥ Authentication
⑦ Authorization (per route)
⑧ Validation (schema)
⑨ Route handler
⑩ Error handler (last)

Rate limiting belongs at the middleware layer. Redis-backed token bucket and sliding window are common (express-rate-limit · @upstash/ratelimit · Spring's Bucket4j).

10. Common pitfalls

Error middleware not catching async throws — Express 4 does not auto-propagate rejections from async handlers. A helper like express-async-errors or a try/catch wrapper is needed. Express 5 improved this.

Sending the response twice — if one middleware sends a response and another sends again, ERR_HTTP_HEADERS_SENT is raised. Tighten the timing of next() calls and returns.

Client IP and proxies — when behind Caddy or nginx, missing X-Forwarded-For trust configuration causes rate limiting to lump everything into the proxy IP.

Information leaks in error messages — make sure stack traces and DB messages are not put into detail directly, and mask in production.

Closing thoughts

Where cross-cutting concerns become half of the code, the value of bundling via wrappers or middleware shows up clearly. Starting with direct try/catch and moving to abstractions when patterns repeat is the safest flow.

Next

  • jobs-apscheduler
  • typeorm-readonly

See RFC 7807 · RFC 9457 (revision) · Express middleware · Spring RestControllerAdvice · FastAPI Dependencies · NestJS Interceptors · Hono Middleware.

More in backend

All in this category →
  • Wrap public OpenAPIs with your own BFF
  • Email Delivery and OTP — SMTP
  • Audit Log — logAdminAction pattern
  • WebSocket and SSE — real-time communication
  • REST API introduction
  • OpenAPI Specification