Global Middleware
Global middleware executes for every request before route handlers, providing cross-cutting functionality like authentication, logging, and CORS.
Each router.use(fn) call appends an entry with an empty path, so it matches every request and runs in the exact order it was registered, before any route matching happens.

Basic Usage
Add global middleware using the use() method:
import { Router } from '@neabyte/deserve'
const router = new Router()
// Log every request, then continue
router.use(async (ctx, next) => {
console.log(`${ctx.get.method()} ${ctx.get.url().href}`)
return await next()
})
await router.serve(8000)Middleware Function Signature
type MiddlewareFn = (
ctx: Context,
next: () => Promise<Response | undefined>
) => Promise<Response | undefined>- Return
await next()- continue to the next middleware or route handler, which allows response modification and inspection - Return
Response- stop processing and return that response immediately - Return
undefined- treated as pass-through so the chain continues as ifnext()were called
Middleware must either call next() and use its result or return a Response. When it does neither, for example never calling next() and returning nothing, the request can hang, so timeoutMs in Router caps the request duration and returns a 503 instead.

Common Global Middleware Patterns
Request Logging
router.use(async (ctx, next) => {
const start = Date.now()
console.log(`${ctx.get.method()} ${ctx.get.url().href} - ${new Date().toISOString()}`)
const response = await next()
const duration = Date.now() - start
console.log(`Completed in ${duration}ms`)
return response
})Authentication
router.use(async (ctx, next) => {
const authHeader = ctx.get.header('authorization')
if (!authHeader) {
return ctx.send.text('Unauthorized', { status: 401 })
}
// Validate token here
const token = authHeader.replace('Bearer ', '')
if (!isValidToken(token)) {
return ctx.send.text('Invalid token', { status: 401 })
}
return await next()
})For a cleaner auth flow, throw an error and let the centralized error handler shape the response instead of building it inline.
Wrapping Middleware With Error Handling
Custom middleware that throws can be wrapped with Wrap.apply, so errors are caught and passed to router.catch() when it is defined:
import { Router, Wrap, type HttpStatusCode } from '@neabyte/deserve'
const router = new Router()
// Wrap so throws reach router.catch
const myAuth = Wrap.apply('Auth', async (ctx, next) => {
// Read API key from header
if (!ctx.get.header('x-api-key')) {
throw new Error('Missing API key')
}
return await next()
})
// Apply middleware and the error handler
router.use(myAuth)
router.catch((ctx, info) => {
return ctx.send.json(
{ error: info.error.message },
{ status: info.statusCode as HttpStatusCode }
)
})
await router.serve(8000)Signature: Wrap.apply(label: string, middleware: MiddlewareFn): MiddlewareFn. When the middleware throws, the error runs through ctx.handleError() so router.catch() is invoked. Every built-in middleware in Mware is already wrapped this way, so a throw inside them carries the middleware label straight to the error handler.
Path-Specific Middleware
Middleware also applies to specific paths, covered in detail in Route-Specific Middleware:
// Runs only for /api paths
router.use('/api', async (ctx, next) => {
console.log('API request:', ctx.get.pathname())
return await next()
})
// Guard /admin paths with an auth check
router.use('/admin', async (ctx, next) => {
if (!isAuthenticated(ctx)) {
return ctx.send.text('Unauthorized', { status: 401 })
}
return await next()
})