Nuxt Server Middleware Patterns: Logging, CORS, Request Context
AI generated
<v/>
{ }
Nuxt 3 · Nitro · Middleware · Backend
Nuxt Server Middleware Patterns
Logging, CORS und Request Context ohne Wiederholung

Wer Logging, CORS Header oder Request IDs in jeder einzelnen Nuxt Server Route separat implementiert, produziert Code Duplikation an zehn Stellen gleichzeitig. Nuxt Server Middleware in server/middleware läuft vor jedem Handler und löst genau diese Querschnittsaufgaben zentral, ohne dass eine Route davon weiß.

17 Min. Lesezeit server/middleware · H3Event · AsyncLocalStorage Nuxt 3 · Nuxt 4

1. Server Middleware vs. Route Middleware

In Nuxt gibt es zwei völlig unterschiedliche Konzepte, die beide "Middleware" heißen, und diese Verwechslung sorgt regelmäßig für Verwirrung. Route Middleware in middleware/ läuft im Vue Router auf Client und Server Seite vor der Navigation zu einer Seite und entscheidet, ob eine Route geladen, umgeleitet oder blockiert wird. Nuxt Server Middleware in server/middleware/ ist etwas grundlegend anderes: sie läuft auf HTTP Ebene, vor jedem einzelnen Request an den Nitro Server, egal ob dieser Request eine Seite, eine API Route oder eine statische Datei anfragt.

Diese Unterscheidung ist wichtig, weil Nuxt Server Middleware nichts von Vue Komponenten, Routen Namen oder Seiten Metadaten weiß. Sie operiert ausschließlich auf dem H3Event, dem rohen Request und Response Objekt. Das macht sie ideal für Aufgaben, die für die gesamte Anwendung gelten müssen, unabhängig davon, ob am Ende eine Seite gerendert oder eine JSON Antwort gesendet wird: Logging, CORS Header, Request IDs, Rate Limiting Zähler oder das frühe Ablehnen offensichtlich fehlerhafter Requests.

Ein Team, das diesen Unterschied nicht kennt, versucht oft, Zugriffskontrolle in Nuxt Server Middleware umzusetzen, wo eigentlich Route Middleware oder ein Auth Modul die passendere Wahl wäre. Für reine API Endpunkte ist Server Middleware dagegen der richtige Ort für einheitliche Querschnittsfunktionen, die vor der eigentlichen Geschäftslogik jeder Route greifen müssen.

2. Ausführungsreihenfolge und Dateibenennung

Jede Datei in server/middleware/ wird bei jedem eingehenden Request automatisch ausgeführt, ohne dass sie irgendwo registriert werden muss. Die Reihenfolge der Ausführung folgt der alphabetischen Sortierung der Dateinamen, weshalb ein gängiges Nuxt Server Middleware Muster numerische Präfixe verwendet: 01.logger.ts, 02.cors.ts, 03.request-id.ts. Diese Präfixe machen die Ausführungsreihenfolge sofort im Dateisystem sichtbar, ohne eine separate Konfigurationsdatei durchsuchen zu müssen.

Anders als ein regulärer Event Handler gibt eine Nuxt Server Middleware normalerweise keinen Wert zurück. Sie liest oder modifiziert das Event, setzt Header, oder wirft im Fehlerfall einen createError. Solange kein Wert zurückgegeben und kein Fehler geworfen wird, läuft die Kette zur nächsten Middleware und schließlich zum eigentlichen Route Handler weiter. Dieses implizite Verkettungsmodell ist bewusst minimalistisch gehalten, verglichen mit Express, wo next() explizit aufgerufen werden muss.


// server/middleware/01.logger.ts
// Runs first for every incoming request
export default defineEventHandler((event) => {
  const start = Date.now()

  event.node.res.on('finish', () => {
    const duration = Date.now() - start
    console.log(
      `[${event.node.req.method}] ${event.node.req.url} — ${event.node.res.statusCode} (${duration}ms)`
    )
  })
})

// server/middleware/02.cors.ts
// Runs second, before route handlers see the request
export default defineEventHandler((event) => {
  setHeader(event, 'Access-Control-Allow-Origin', 'https://shop.mironsoft.de')
  setHeader(event, 'Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')

  if (event.node.req.method === 'OPTIONS') {
    setResponseStatus(event, 204)
    return ''
  }
})

3. Request Logging als Middleware

Strukturiertes Request Logging ist der klassische Einstieg in Nuxt Server Middleware, weil der Nutzen sofort sichtbar wird: jeder Request, jede Methode, jeder Status Code und jede Antwortzeit landet in einer einzigen Zeile Log Ausgabe, ohne dass eine einzelne Route dafür angepasst werden muss. Der Trick liegt im finish Event des zugrunde liegenden Node Response Objekts, das erst feuert, wenn die Antwort vollständig gesendet wurde, sodass Statuscode und Dauer korrekt erfasst werden.

In Produktion lohnt sich strukturiertes JSON Logging statt reiner Textzeilen, damit ein Log Aggregator wie Loki oder Datadog die Felder direkt indexieren kann. Eine Nuxt Server Middleware für Logging sollte zusätzlich sensible Header wie authorization oder cookie aus dem geloggten Objekt entfernen, bevor sie irgendwo landen, sonst wandern Zugangsdaten versehentlich in Log Dateien, die von mehr Personen gelesen werden als der eigentliche Request selbst.


// server/middleware/01.logger.ts
// Structured logging with sensitive headers stripped
export default defineEventHandler((event) => {
  const start = Date.now()
  const { method, url } = event.node.req

  event.node.res.on('finish', () => {
    const headers = { ...getHeaders(event) }
    delete headers.authorization
    delete headers.cookie

    console.log(JSON.stringify({
      method,
      url,
      status: event.node.res.statusCode,
      durationMs: Date.now() - start,
      requestId: event.context.requestId,
    }))
  })
})

4. CORS Handling zentral lösen

CORS Fehler sind eine der häufigsten Quellen für Frustration, wenn ein Frontend auf einer anderen Domain als die Nuxt Server Route API läuft. Statt CORS Header in jeder einzelnen Route manuell zu setzen, gehört diese Logik in eine einzige Nuxt Server Middleware, die konsequent vor allen API Routen läuft. Wichtig ist die korrekte Behandlung von Preflight Requests: Browser senden vor bestimmten Cross Origin Requests automatisch einen OPTIONS Request, der mit Status 204 und den erlaubten Methoden beantwortet werden muss, ohne die eigentliche Route zu erreichen.

Für Multi Tenant Anwendungen mit mehreren erlaubten Origins reicht ein einzelner statischer Access-Control-Allow-Origin Header nicht aus. Die Nuxt Server Middleware muss dann den Origin Header des Requests gegen eine Whitelist prüfen und dynamisch den passenden Wert zurückspiegeln, statt pauschal * zu setzen, was bei Requests mit Credentials ohnehin vom Browser abgelehnt wird.


// server/middleware/02.cors.ts
const allowedOrigins = new Set([
  'https://shop.mironsoft.de',
  'https://admin.mironsoft.de',
])

export default defineEventHandler((event) => {
  const origin = getHeader(event, 'origin')

  if (origin && allowedOrigins.has(origin)) {
    setHeader(event, 'Access-Control-Allow-Origin', origin)
    setHeader(event, 'Access-Control-Allow-Credentials', 'true')
    setHeader(event, 'Vary', 'Origin')
  }

  setHeader(event, 'Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS')
  setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')

  // Preflight request: answer immediately, never reach the actual route
  if (event.node.req.method === 'OPTIONS') {
    setResponseStatus(event, 204)
    return ''
  }
})

5. Request Context mit AsyncLocalStorage

Eine fortgeschrittene Anwendung von Nuxt Server Middleware ist das Erzeugen eines Request Context, der über die gesamte Verarbeitungskette hinweg verfügbar bleibt, ohne dass jede Funktion den Request explizit als Parameter durchreichen muss. Node bringt dafür AsyncLocalStorage mit: eine Middleware erzeugt zu Beginn jedes Requests eine eindeutige Request ID, speichert sie im Storage, und jede tiefer liegende Funktion, auch Services ohne Zugriff auf das H3Event, kann sie über asyncLocalStorage.getStore() auslesen.

Der praktische Nutzen zeigt sich beim Debugging verteilter Logs: taucht ein Fehler in einer tief verschachtelten Service Funktion auf, lässt sich über die Request ID exakt nachvollziehen, zu welchem eingehenden Request der Fehler gehört, auch wenn zwischen Nuxt Server Middleware und der fehlerhaften Funktion mehrere Ebenen liegen. Ohne diesen Mechanismus müsste die Request ID manuell durch jede Funktionssignatur gereicht werden, was den Code unnötig aufbläht.


// server/utils/requestContext.ts
import { AsyncLocalStorage } from 'node:async_hooks'
import { randomUUID } from 'node:crypto'

interface RequestContext {
  requestId: string
}

export const requestContext = new AsyncLocalStorage<RequestContext>()

// server/middleware/00.request-context.ts
// Must run before logger and any route handler
export default defineEventHandler((event) => {
  const requestId = getHeader(event, 'x-request-id') ?? randomUUID()
  event.context.requestId = requestId
  setHeader(event, 'X-Request-Id', requestId)
})

// Usage deep inside a service, without passing the event around
export function logWithContext(message: string) {
  const store = requestContext.getStore()
  console.log(`[${store?.requestId ?? 'no-request'}] ${message}`)
}

6. Auth Header Prüfung ohne Redirect

Für reine API Endpunkte, die keine Weiterleitung auf eine Login Seite kennen, ist eine schlanke Nuxt Server Middleware zur Header Prüfung oft ausreichend, ganz ohne ein vollständiges Auth Modul. Die Middleware prüft, ob ein Bearer Token vorhanden und syntaktisch gültig ist, und speichert den dekodierten Nutzer im event.context, damit nachfolgende Route Handler ihn ohne erneutes Parsen nutzen können. Fehlt der Token bei einer geschützten Route, wirft die Middleware direkt einen 401 Fehler, statt eine Umleitung anzustoßen, die bei einer JSON API ohnehin nicht sinnvoll wäre.

Wichtig ist, dass diese Middleware selektiv arbeitet: nicht jede Route soll authentifiziert werden, öffentliche Endpunkte wie Health Checks oder öffentliche Produktlisten müssen ausgenommen bleiben. Ein einfaches Muster prüft den Pfad des Requests gegen eine Liste geschützter Präfixe, bevor die eigentliche Token Prüfung überhaupt anläuft, sodass öffentliche Nuxt Server Routes unverändert erreichbar bleiben.


// server/middleware/03.auth-check.ts
const protectedPrefixes = ['/api/admin', '/api/orders']

export default defineEventHandler(async (event) => {
  const path = event.node.req.url ?? ''
  const isProtected = protectedPrefixes.some((prefix) => path.startsWith(prefix))

  if (!isProtected) return

  const authHeader = getHeader(event, 'authorization')
  if (!authHeader?.startsWith('Bearer ')) {
    throw createError({ statusCode: 401, statusMessage: 'Missing bearer token' })
  }

  const token = authHeader.slice('Bearer '.length)
  const user = await verifyToken(token) // throws on invalid/expired token
  event.context.user = user
})

7. Fehlerbehandlung in der Middleware Kette

Ein Fehler, der in einer Nuxt Server Middleware geworfen wird, unterbricht die Kette sofort und erreicht weder nachfolgende Middleware noch den eigentlichen Route Handler. Das ist meist erwünscht, etwa bei einer fehlgeschlagenen Auth Prüfung, kann aber zu unerwarteten Ergebnissen führen, wenn eine Logging Middleware nach der fehlgeschlagenen Auth Middleware liegt und den Request dadurch nie zu Gesicht bekommt.

Die Reihenfolge in server/middleware/ entscheidet also nicht nur über die Ausführung, sondern auch darüber, welche Middleware einen fehlgeschlagenen Request überhaupt noch sieht. Ein bewährtes Muster: Logging und Request Context so früh wie möglich platzieren, damit sie jeden Request erfassen, auch solche, die später an einer Auth Prüfung scheitern. Ein globaler Error Handler in server/plugins/ fängt zusätzlich alle unbehandelten Fehler ab, egal ob sie aus Nuxt Server Middleware oder aus einem Route Handler stammen.

8. Performance und wann Middleware vermeiden

Da jede Datei in server/middleware/ bei jedem einzelnen Request ausgeführt wird, unabhängig davon, ob die Route sie überhaupt braucht, summiert sich der Overhead mit jeder zusätzlichen Nuxt Server Middleware. Für statische Assets, die Nitro direkt aus dem Dateisystem serviert, läuft die Middleware Kette ebenfalls, was bei aufwendigen Prüfungen unnötige Latenz erzeugt. Teure Operationen wie Datenbankabfragen gehören daher nicht in eine global laufende Middleware, sondern in den spezifischen Route Handler, der sie tatsächlich benötigt.

Eine bewährte Faustregel: Nuxt Server Middleware sollte ausschließlich für Aufgaben genutzt werden, die für praktisch jeden Request relevant sind, wie Logging, CORS oder Request IDs. Alles, was nur für eine Teilmenge der Routen gilt, etwa spezifische Validierung oder ressourcenintensive Prüfungen, gehört als expliziter Aufruf in den jeweiligen Handler, wo die Bedingung sichtbar bleibt, statt implizit in einer global laufenden Datei zu verschwinden.

9. Middleware Patterns im Vergleich

Nicht jedes Querschnittsproblem gehört in dieselbe Schicht. Die folgende Tabelle ordnet typische Aufgaben den passenden Mechanismen zu und zeigt, wann Nuxt Server Middleware tatsächlich die richtige Wahl ist und wann ein anderes Konzept besser passt.

Aufgabe Mechanismus Läuft für Begründung
Request Logging Server Middleware Jeden Request Zentral, keine Route Kenntnis nötig
CORS Header Server Middleware Jeden API Request Einheitliche Origin Prüfung
Body Validierung Route Handler Nur betroffene Route Schema unterscheidet sich pro Route
Seiten Zugriffsschutz Route Middleware (Client/Server) Nur Seiten Navigation Kennt Vue Router und Redirects
API Token Prüfung Server Middleware Geschützte API Präfixe Kein Redirect Konzept in JSON APIs

Die Tabelle macht deutlich, dass Nuxt Server Middleware überall dort ihre Stärke ausspielt, wo eine Aufgabe für praktisch jeden Request auf HTTP Ebene relevant ist, unabhängig von Vue Komponenten oder Seiten Navigation. Für alles, was seitenspezifisch ist oder Redirects im Browser auslöst, bleibt Route Middleware die richtige Wahl.

Mironsoft

Vue.js und Nuxt Entwicklung für produktive Frontends und Backends

Logging und CORS an zehn Stellen kopiert?

Wir bauen Nuxt Server Middleware, die Logging, CORS und Request Context zentral löst, sodass eure API Routen schlank und fokussiert bleiben.

Middleware Audit

Bestehende Querschnittslogik konsolidieren und in server/middleware verschieben

Observability

Request Context und strukturiertes Logging für Produktions Debugging

Sicherheit

CORS und Token Prüfung ohne doppelten Code in jeder Route

10. Zusammenfassung

Nuxt Server Middleware in server/middleware/ ist der richtige Ort für Aufgaben, die auf HTTP Ebene für praktisch jeden Request gelten: Logging, CORS Header, Request Context und einfache Header basierte Zugriffsprüfungen. Sie unterscheidet sich fundamental von Route Middleware, die im Vue Router läuft und Seiten Navigation steuert. Die Ausführungsreihenfolge folgt der alphabetischen Dateisortierung, weshalb numerische Präfixe wie 01., 02. die Reihenfolge im Dateisystem sichtbar machen.

AsyncLocalStorage erlaubt Request Context, der über die gesamte Verarbeitungskette verfügbar bleibt, ohne dass jede Funktion den Request explizit durchreicht. Da jede Nuxt Server Middleware bei jedem Request läuft, gehören teure Operationen nicht hierhin, sondern in den spezifischen Route Handler. Wer diese Grenzen respektiert, baut eine API, in der Querschnittslogik zentral und Geschäftslogik lokal in den jeweiligen Routen bleibt.

Nuxt Server Middleware Patterns — Das Wichtigste auf einen Blick

Reihenfolge

Alphabetisch nach Dateiname, numerische Präfixe wie 01., 02. machen sie explizit.

Einsatzgebiet

Logging, CORS, Request IDs, einfache Token Prüfung, alles auf HTTP Ebene für jeden Request.

Context

AsyncLocalStorage teilt Request ID und Nutzerdaten ohne Parameter Durchreichung.

Performance

Teure Operationen gehören in den Route Handler, nicht in global laufende Middleware.

11. FAQ: Nuxt Server Middleware Patterns

1Server Middleware vs. Route Middleware?
Server Middleware läuft auf HTTP Ebene vor jedem Request. Route Middleware läuft im Vue Router vor Seiten Navigation und kennt Redirects.
2Reihenfolge der Middleware Dateien?
Alphabetisch nach Dateiname. Numerische Präfixe wie 01., 02. machen die Reihenfolge im Dateisystem sichtbar.
3Request Dauer zentral loggen?
Über das finish Event des Node Response Objekts, das erst nach vollständiger Antwort feuert.
4CORS Preflight korrekt behandeln?
OPTIONS Request mit Status 204 und erlaubten Methoden beantworten, ohne die eigentliche Route zu erreichen.
5Wofür AsyncLocalStorage?
Macht Daten über eine asynchrone Aufrufkette verfügbar, ohne sie manuell durchzureichen, praktisch für Request IDs.
6Zugriffsschutz für API Routen?
Ja, Bearer Token Prüfung ohne Redirect. Fehlender oder ungültiger Token wirft direkt einen 401 Fehler.
7Was passiert bei einem Fehler in der Middleware?
Die Kette bricht sofort ab, nachfolgende Middleware und der Handler werden nicht erreicht.
8Warum keine Datenbankabfragen in Middleware?
Läuft bei jedem Request, auch für statische Assets. Teure Operationen erzeugen unnötige Latenz.
9Läuft sie auch für statische Assets?
Ja, für jeden Request an den Nitro Server, unabhängig vom Ziel der Anfrage.
10Wie teste ich Server Middleware isoliert?
Mit Integrationstests über @nuxt/test-utils und $fetch gegen einen laufenden Test Server.