HTTP Router APIs
The HTTP router APIs let you register middleware and permissions that apply across multiple HTTP routes. Use these for cross-cutting concerns like authentication, logging, and access control.
For route-specific middleware and permissions, see wireHTTP configuration options.
addHTTPMiddleware​
Applies middleware globally or to routes matching a prefix.
import { addHTTPMiddleware } from '#pikku/pikku-types.gen.js'
import { corsMiddleware, responseTime } from './middleware.js'
// All HTTP routes
addHTTPMiddleware([corsMiddleware, responseTime])
// Routes starting with /admin
addHTTPMiddleware('/admin', [requireAuth, auditLog])
Parameters​
- middleware - Array of middleware functions when used globally
- route (
string
) - Route prefix pattern (first parameter) - middleware - Array of middleware for the prefix (second parameter)
Global HTTP Middleware​
import { addHTTPMiddleware, pikkuMiddleware } from '#pikku/pikku-types.gen.js'
const cors = pikkuMiddleware(async (_services, interaction, next) => {
if (interaction.http) {
interaction.http.response.setHeader('Access-Control-Allow-Origin', '*')
interaction.http.response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
}
await next()
})
const responseTime = pikkuMiddleware(async (_services, interaction, next) => {
const start = Date.now()
await next()
if (interaction.http) {
const duration = Date.now() - start
interaction.http.response.setHeader('X-Response-Time', `${duration}ms`)
}
})
// Apply to all HTTP routes
addHTTPMiddleware([cors, responseTime])
Prefix-Based Middleware​
const adminAuth = pikkuMiddleware(async ({ jwt, userSession }, interaction, next) => {
if (!interaction.http) return await next()
const token = interaction.http.request.getHeader('Authorization')
if (!token) {
interaction.http.response.setStatus(401).json({ error: 'Unauthorized' })
return
}
try {
const payload = await jwt.verify(token.replace('Bearer ', ''))
await userSession.set(payload)
await next()
} catch (e) {
interaction.http.response.setStatus(401).json({ error: 'Invalid token' })
}
})
const rateLimit = pikkuMiddleware(async ({ cache }, interaction, next) => {
if (!interaction.http) return await next()
const ip = interaction.http.request.getHeader('x-forwarded-for') || 'unknown'
const key = `ratelimit:${ip}`
const count = (await cache.get(key)) || 0
if (count > 100) {
interaction.http.response.setStatus(429).json({ error: 'Too many requests' })
return
}
await cache.set(key, count + 1, { ttl: 60 })
await next()
})
// Admin routes need authentication
addHTTPMiddleware('/admin', [adminAuth])
// API routes are rate limited
addHTTPMiddleware('/api', [rateLimit])
addHTTPPermission​
Applies permissions globally or to routes matching a prefix.
import { addHTTPPermission } from '#pikku/pikku-types.gen.js'
import { requireAuth, requireAdmin } from './permissions.js'
// Routes starting with /admin require auth + admin
addHTTPPermission('/admin', {
auth: requireAuth,
admin: requireAdmin,
})
// All HTTP routes require authentication
addHTTPPermission('*', {
auth: requireAuth,
})
Parameters​
- route (
string
) - Route prefix pattern, or'*'
for global - permissions (
Record<string, PikkuPermission>
) - Named permissions to apply
Example with Multiple Permissions​
import { pikkuPermission } from '#pikku/pikku-types.gen.js'
const requireAuth = pikkuPermission(async (_services, _data, session) => {
return session?.userId != null
})
const requireAdmin = pikkuPermission(async (_services, _data, session) => {
return session?.role === 'admin'
})
const requirePremium = pikkuPermission(async ({ database }, _data, session) => {
if (!session?.userId) return false
const user = await database.query('users', {
where: { id: session.userId }
})
return user?.isPremium === true
})
// Admin routes need both auth and admin role
addHTTPPermission('/admin', {
auth: requireAuth,
admin: requireAdmin,
})
// Premium features need auth and premium subscription
addHTTPPermission('/api/premium', {
auth: requireAuth,
premium: requirePremium,
})
Route Pattern Matching​
Prefix patterns match routes that start with the given path:
Pattern | Matches | Examples |
---|---|---|
/admin | All routes starting with /admin | /admin/users , /admin/settings |
/api/v1 | All routes starting with /api/v1 | /api/v1/users , /api/v1/posts |
* | All routes (global) | Any HTTP route |
Note: These are prefix matches, not glob patterns. /admin
matches /admin/users
and /admin/settings/profile
.
Middleware Execution Order​
See Middleware for the complete execution order across all scopes.
For HTTP routes, middleware runs in this order:
- Global HTTP middleware -
addHTTPMiddleware([...])
- Prefix HTTP middleware -
addHTTPMiddleware('/prefix', [...])
- Wire-specific middleware -
wireHTTP({ middleware: [...] })
- Function-level middleware -
pikkuFunc({ middleware: [...] })
Common Patterns​
Authentication Middleware​
Extract JWT tokens and set user session:
const jwtAuth = pikkuMiddleware(async ({ jwt, userSession }, interaction, next) => {
if (!interaction.http) return await next()
const token = interaction.http.request.getHeader('Authorization')?.replace('Bearer ', '')
if (token) {
try {
const payload = await jwt.verify(token)
await userSession.set({
userId: payload.userId,
role: payload.role
})
} catch (error) {
// Invalid token - continue without session
// Let function-level auth/permissions handle it
}
}
await next()
})
// Apply to all protected routes
addHTTPMiddleware('/api', [jwtAuth])
Request Logging​
Log all requests with timing:
const requestLogger = pikkuMiddleware(async ({ logger }, interaction, next) => {
if (!interaction.http) return await next()
const start = Date.now()
const method = interaction.http.request.method
const url = interaction.http.request.url
logger.info(`${method} ${url} - Started`)
await next()
const duration = Date.now() - start
const status = interaction.http.response.status || 200
logger.info(`${method} ${url} - ${status} (${duration}ms)`)
})
// Log all HTTP requests
addHTTPMiddleware([requestLogger])
Security Headers​
Add security headers to all responses:
const securityHeaders = pikkuMiddleware(async (_services, interaction, next) => {
await next()
if (interaction.http) {
interaction.http.response.setHeader('X-Content-Type-Options', 'nosniff')
interaction.http.response.setHeader('X-Frame-Options', 'DENY')
interaction.http.response.setHeader('X-XSS-Protection', '1; mode=block')
interaction.http.response.setHeader('Strict-Transport-Security', 'max-age=31536000')
}
})
// Apply to all routes
addHTTPMiddleware([securityHeaders])
Next Steps​
- Middleware - Understanding middleware concepts
- Permission Guards - Understanding permissions
- wireHTTP - Route-specific configuration