Request ID
@vafast/request-id generates a unique ID for each request: it writes it to req.id, injects it into the handler context via next({ requestId }), and echoes it in a response header (default X-Request-Id).
Installation
bash
npm install @vafast/request-idQuick Start
typescript
import { Server, defineRoute, defineRoutes, serve } from 'vafast'
import { requestId } from '@vafast/request-id'
const routes = defineRoutes([
defineRoute({
method: 'GET',
path: '/',
handler: ({ requestId: id, req }) => ({
fromContext: id,
fromReq: req.id,
}),
}),
])
const server = new Server(routes)
server.use(requestId())
serve({ fetch: server.fetch, port: 3000 })The response will include:
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000Usage
Reading It in a Handler
The middleware calls next({ requestId }), so handlers can destructure it directly; it's also attached to req.id:
typescript
defineRoute({
method: 'GET',
path: '/work',
handler: ({ requestId: id, req }) => {
console.log(id, req.id) // same value
return { ok: true }
},
})Type-Safe Helper
typescript
import { getRequestId } from '@vafast/request-id'
const id = getRequestId(req) // string | undefinedCustom Generator / Response Header
typescript
import { requestId } from '@vafast/request-id'
server.use(
requestId({
generator: () => `req-${Date.now()}`,
headerName: 'X-Correlation-Id',
}),
)Distributed Tracing (Reusing an Upstream ID)
By default useExisting: true: if the incoming request already has the header, it's reused instead of generating a new one.
typescript
server.use(
requestId({
headerName: 'X-Request-Id',
existingHeaderName: 'X-Trace-Id', // read from a different request header
}),
)
// always generate a new ID
server.use(requestId({ useExisting: false }))Combining with request-logger
typescript
import { requestId } from '@vafast/request-id'
import { requestLogger } from '@vafast/request-logger'
server.use(requestId()) // mount first so req.id is set
server.use(
requestLogger({
url: process.env.LOG_INGEST_URL!,
service: 'my-server',
}),
)request-logger reads req.id first, then falls back to the x-request-id header.
Full API Parameters
requestId(options?)
Returns a Vafast middleware.
RequestIdOptions
| Parameter | Type | Default | Description |
|---|---|---|---|
generator | () => string | crypto.randomUUID() | Custom ID generator |
headerName | string | 'X-Request-Id' | Name of the response header to write |
useExisting | boolean | true | Whether to reuse the ID from the incoming request header |
existingHeaderName | string | Same as headerName | Request header to read an existing ID from |
Behavior summary:
- If
useExisting, read the incoming ID fromexistingHeaderName - Otherwise call
generator() - Assign it to
req.idand callnext({ requestId: id }) - Clone the response, set
headerNameand return it
getRequestId(req)
typescript
getRequestId(req: Request): string | undefinedReads req.id; undefined if the middleware isn't mounted.
Types
typescript
type IdGenerator = () => string
interface RequestIdOptions {
generator?: IdGenerator
headerName?: string
useExisting?: boolean
existingHeaderName?: string
}The package also adds an optional id?: string to Request via declare global.
Best Practices
- Mount early: before any middleware that needs to correlate logs (especially
request-logger) - Use one header name across the chain: agree on
X-Request-Idbetween gateway and services and keepuseExisting: true - Include the ID in business logs:
logger.info({ requestId: id }, '...')to correlate with access logs - For short IDs, use a custom
generator(e.g. nanoid) instead of changing the globalcrypto
Notes
- The middleware creates a new Response to write the header; the original
response.bodystream is forwarded, so don't rely on the same body after consuming it downstream - With
useExisting: true, the incoming header is trusted without format validation; validate or sanitize it at the gateway @vafast/loggeris not request middleware; application logs need to includerequestIdthemselves- Both the context
requestIdandreq.idare available in handlers and hold the same string