| title | Middleware Reference |
|---|---|
| sidebar_label | Middleware |
| description | Complete reference for all built-in middleware components in NeuroLink server adapters |
| sidebar_position | 6 |
| keywords | middleware, timing, request-id, error-handling, security, logging, compression, rate-limit, cache, validation, auth, abort-signal, deprecation |
NeuroLink server adapters provide a comprehensive set of middleware components for common server operations. All middleware follows a consistent pattern and can be composed together for your specific use case.
| Middleware | Purpose | Order |
|---|---|---|
createTimingMiddleware() |
Measures request duration | 0 |
createRequestIdMiddleware() |
Generates/propagates request IDs | 0 |
createErrorHandlingMiddleware() |
Centralized error catching and formatting | 1 |
createSecurityHeadersMiddleware() |
Adds security headers | 2 |
createLoggingMiddleware() |
Request/response logging | 3 |
createRateLimitMiddleware() |
Rate limiting | 5 |
createAbortSignalMiddleware() |
Client disconnection detection | 5 |
createCompressionMiddleware() |
Response compression signaling | 5 |
createAuthMiddleware() |
Authentication | 10 |
createRequestValidationMiddleware() |
Request body/query/params validation | 15 |
createCacheMiddleware() |
Response caching | 20 |
createMCPBodyAttachmentMiddleware() |
MCP SDK body compatibility | 10 |
createDeprecationMiddleware() |
RFC 8594 deprecation headers | 100 |
The order value determines execution sequence - lower numbers run first.
Measures request duration and adds timing headers to responses.
import { createTimingMiddleware } from "@juspay/neurolink";
server.registerMiddleware(createTimingMiddleware());| Header | Description | Example |
|---|---|---|
X-Response-Time |
Total request processing time in milliseconds | 45.23ms |
Server-Timing |
Standard Server-Timing header for performance monitoring | total;dur=45.23 |
- Always recommended for production servers
- Essential for performance monitoring and debugging
- Works with browser Developer Tools and APM systems
Ensures every request has a unique identifier for tracing and debugging.
type RequestIdOptions = {
/** Header name to check for existing ID (default: "x-request-id") */
headerName?: string;
/** Prefix for generated IDs (default: "req") */
prefix?: string;
/** Custom ID generator function */
generator?: () => string;
};import { createRequestIdMiddleware } from "@juspay/neurolink";
// Basic usage
server.registerMiddleware(createRequestIdMiddleware());
// With custom options
server.registerMiddleware(
createRequestIdMiddleware({
headerName: "x-correlation-id",
prefix: "neuro",
generator: () => `neuro-${crypto.randomUUID()}`,
}),
);| Header | Direction | Description |
|---|---|---|
X-Request-ID |
Request | Propagates existing ID from client (if present) |
X-Request-ID |
Response | Returns request ID for client-side correlation |
- Always recommended for production servers
- Essential for distributed tracing
- Enables log correlation across services
- Helps with debugging and support tickets
Catches errors and formats them consistently across all routes.
type ErrorHandlingOptions = {
/** Include stack trace in error response (default: false) */
includeStack?: boolean;
/** Custom error handler function */
onError?: (error: Error, ctx: ServerContext) => unknown;
/** Log errors to console (default: true) */
logErrors?: boolean;
};import { createErrorHandlingMiddleware } from "@juspay/neurolink";
// Basic usage
server.registerMiddleware(createErrorHandlingMiddleware());
// Development mode with stack traces
server.registerMiddleware(
createErrorHandlingMiddleware({
includeStack: process.env.NODE_ENV === "development",
logErrors: true,
}),
);
// With custom error handler
server.registerMiddleware(
createErrorHandlingMiddleware({
onError: (error, ctx) => ({
error: {
code: "CUSTOM_ERROR",
message: error.message,
requestId: ctx.requestId,
},
}),
}),
);{
"error": {
"code": "HTTP_500",
"message": "Internal server error",
"stack": "Error: Something went wrong\n at ..." // Only if includeStack: true
},
"metadata": {
"requestId": "req-1706745600000-abc123",
"timestamp": "2024-02-01T12:00:00.000Z"
}
}- Always recommended for production servers
- Provides consistent error responses
- Prevents leaking sensitive information in production
- Enable stack traces only in development
Adds common security headers to protect against various web vulnerabilities.
type SecurityHeadersOptions = {
/** Content Security Policy directive */
contentSecurityPolicy?: string;
/** X-Frame-Options (default: "DENY") */
frameOptions?: "DENY" | "SAMEORIGIN" | false;
/** X-Content-Type-Options (default: "nosniff") */
contentTypeOptions?: "nosniff" | false;
/** HSTS max-age in seconds (default: 31536000 = 1 year) */
hstsMaxAge?: number | false;
/** Referrer-Policy (default: "strict-origin-when-cross-origin") */
referrerPolicy?: string | false;
/** Additional custom headers */
customHeaders?: Record<string, string>;
};import { createSecurityHeadersMiddleware } from "@juspay/neurolink";
// Basic usage with defaults
server.registerMiddleware(createSecurityHeadersMiddleware());
// With custom configuration
server.registerMiddleware(
createSecurityHeadersMiddleware({
contentSecurityPolicy:
"default-src 'self'; script-src 'self' 'unsafe-inline'",
frameOptions: "SAMEORIGIN",
hstsMaxAge: 63072000, // 2 years
customHeaders: {
"X-Custom-Header": "value",
},
}),
);| Header | Default Value | Description |
|---|---|---|
X-Frame-Options |
DENY |
Prevents clickjacking |
X-Content-Type-Options |
nosniff |
Prevents MIME sniffing |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Enforces HTTPS |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer information |
X-XSS-Protection |
1; mode=block |
Legacy XSS protection |
Content-Security-Policy |
Not set by default | Content security policy |
- Always recommended for production servers
- Required for security compliance (OWASP, PCI-DSS)
- Configure CSP based on your application needs
- Disable HSTS initially if not ready for HTTPS-only
Logs request and response information with configurable detail levels.
type LoggingOptions = {
/** Log request body (default: false) */
logBody?: boolean;
/** Log response body (default: false) */
logResponse?: boolean;
/** Custom logger instance */
logger?: {
info: (message: string, data?: unknown) => void;
error: (message: string, data?: unknown) => void;
};
/** Paths to skip logging (default: ["/health", "/ready", "/metrics"]) */
skipPaths?: string[];
};import { createLoggingMiddleware } from "@juspay/neurolink";
// Basic usage
server.registerMiddleware(createLoggingMiddleware());
// Development mode with body logging
server.registerMiddleware(
createLoggingMiddleware({
logBody: process.env.NODE_ENV === "development",
logResponse: process.env.NODE_ENV === "development",
skipPaths: ["/api/health", "/api/ready"],
}),
);
// With custom logger (e.g., Winston, Pino)
import pino from "pino";
const logger = pino();
server.registerMiddleware(
createLoggingMiddleware({
logger: {
info: (msg, data) => logger.info(data, msg),
error: (msg, data) => logger.error(data, msg),
},
}),
);Request Log:
[Request] POST /api/agent/execute { requestId: "req-123", method: "POST", path: "/api/agent/execute" }
Response Log:
[Response] POST /api/agent/execute { requestId: "req-123", duration: "45ms", status: 200 }
Error Log:
[Error] POST /api/agent/execute { requestId: "req-123", duration: "12ms", error: "Invalid input", status: 400 }
- Always recommended for production servers
- Disable body logging in production for performance and privacy
- Use structured logging (JSON) for log aggregation systems
- Skip health check endpoints to reduce noise
Signals compression preferences to adapters for response compression.
type CompressionOptions = {
/** Minimum response size to compress in bytes (default: 1024) */
threshold?: number;
/** Content types to compress */
contentTypes?: string[];
};import { createCompressionMiddleware } from "@juspay/neurolink";
// Basic usage
server.registerMiddleware(createCompressionMiddleware());
// With custom configuration
server.registerMiddleware(
createCompressionMiddleware({
threshold: 2048, // Only compress responses > 2KB
contentTypes: ["text/", "application/json", "application/xml"],
}),
);This middleware stores compression preferences in the request context metadata. The actual compression is handled by the underlying framework (Hono, Express, etc.) or a reverse proxy.
- Recommended for responses larger than 1KB
- Works best with text-based content (JSON, HTML, XML)
- Consider disabling for already-compressed content (images, videos)
- Often handled at reverse proxy level (nginx, CloudFlare)
Provides client disconnection handling for long-running requests using AbortController.
type AbortSignalMiddlewareOptions = {
/** Callback when abort is triggered */
onAbort?: (ctx: ServerContext) => void;
/** Request timeout in milliseconds */
timeout?: number;
};import { createAbortSignalMiddleware } from "@juspay/neurolink";
// Basic usage
server.registerMiddleware(createAbortSignalMiddleware());
// With timeout and abort callback
server.registerMiddleware(
createAbortSignalMiddleware({
timeout: 30000, // 30 seconds
onAbort: (ctx) => {
console.log(`Request ${ctx.requestId} was aborted`);
},
}),
);server.registerRoute({
method: "POST",
path: "/api/long-running",
handler: async (ctx) => {
const signal = ctx.abortSignal;
// Pass signal to cancellable operations
const result = await longRunningOperation({ signal });
// Check if aborted before continuing
if (signal?.aborted) {
throw new Error("Request was cancelled");
}
return result;
},
});For Express applications, use the specialized Express middleware:
import { createExpressAbortMiddleware } from "@juspay/neurolink";
app.use(
createExpressAbortMiddleware({
onAbort: () => console.log("Client disconnected"),
}),
);
app.get("/api/stream", (req, res) => {
const signal = res.locals.abortSignal;
// Use signal for cancellation
});- Long-running operations (AI generation, file processing)
- Streaming endpoints where client might disconnect
- Operations that should be cancelled on timeout
- Preventing resource waste on abandoned requests
Bridges the gap between Fastify's body parsing and the MCP SDK's body access pattern.
import { createMCPBodyAttachmentMiddleware } from "@juspay/neurolink";
// General middleware for any adapter
server.registerMiddleware(createMCPBodyAttachmentMiddleware());For optimal Fastify integration, use the dedicated preHandler hook:
import { fastifyMCPBodyHook } from "@juspay/neurolink";
fastify.addHook("preHandler", fastifyMCPBodyHook);The MCP SDK reads the request body from request.raw.body, but Fastify parses the body separately into request.body. This middleware attaches the parsed body to request.raw.body for MCP SDK compatibility.
- Required when using MCP routes with Fastify
- Not needed for Hono, Express, or Koa adapters
- Applied automatically by the Fastify adapter
Adds RFC 8594 compliant deprecation headers to responses for deprecated routes.
type DeprecationConfig = {
/** Array of route definitions to check for deprecation */
routes: RouteDefinition[];
/** Custom header name for deprecation notice (default: "X-Deprecation-Notice") */
noticeHeader?: string;
/** Include Link header for alternative routes (default: true) */
includeLink?: boolean;
};
type RouteDeprecation = {
enabled: boolean;
since?: string; // Version when deprecated
removeIn?: string; // Version when removed
alternative?: string; // Replacement endpoint
message?: string; // Custom message
};import { createDeprecationMiddleware } from "@juspay/neurolink";
const routes = [
{
method: "GET",
path: "/api/v1/users",
handler: handleUsers,
deprecated: {
enabled: true,
since: "2.0.0",
removeIn: "3.0.0",
alternative: "/api/v2/users",
message: "Use /api/v2/users for improved performance",
},
},
];
server.registerMiddleware(createDeprecationMiddleware({ routes }));| Header | Description | Example |
|---|---|---|
Deprecation |
RFC 8594 deprecation indicator | true |
Sunset |
When the endpoint will be removed (HTTP-date) | Sun, 01 Jun 2025 00:00:00 GMT |
Link |
Alternative endpoint with rel="successor-version" | </api/v2/users>; rel="successor-version" |
X-Deprecation-Notice |
Human-readable deprecation message | Use /api/v2/users for improved performance |
- API versioning migrations
- Feature deprecation announcements
- Gradual API evolution
- Compliance with RFC 8594
Provides configurable rate limiting with multiple algorithms.
type RateLimitMiddlewareConfig = {
/** Maximum requests per window */
maxRequests: number;
/** Time window in milliseconds */
windowMs: number;
/** Custom error message */
message?: string;
/** Skip rate limiting for certain paths */
skipPaths?: string[];
/** Custom key generator (default: IP address) */
keyGenerator?: (ctx: ServerContext) => string;
/** Custom response handler for rate limit exceeded */
onRateLimitExceeded?: (ctx: ServerContext, retryAfter: number) => unknown;
/** Custom rate limit store (default: in-memory) */
store?: RateLimitStore;
};import {
createRateLimitMiddleware,
createSlidingWindowRateLimitMiddleware,
InMemoryRateLimitStore,
} from "@juspay/neurolink";
// Fixed window rate limiting
server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 100,
windowMs: 15 * 60 * 1000, // 15 minutes
skipPaths: ["/api/health"],
}),
);
// Sliding window rate limiting (more accurate)
server.registerMiddleware(
createSlidingWindowRateLimitMiddleware({
maxRequests: 100,
windowMs: 15 * 60 * 1000,
subWindows: 10, // Number of sub-windows for smoothing
}),
);
// Rate limit by user ID instead of IP
server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 1000,
windowMs: 60 * 60 * 1000, // 1 hour
keyGenerator: (ctx) =>
ctx.user?.id || ctx.headers["x-forwarded-for"] || "unknown",
}),
);| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit |
Maximum requests allowed per window | 100 |
X-RateLimit-Remaining |
Requests remaining in current window | 95 |
X-RateLimit-Reset |
Unix timestamp when the window resets | 1706746200 |
Retry-After |
Seconds to wait (only on 429 response) | 300 |
import Redis from "ioredis";
import type { RateLimitStore, RateLimitEntry } from "@juspay/neurolink";
class RedisRateLimitStore implements RateLimitStore {
constructor(private redis: Redis) {}
async get(key: string): Promise<RateLimitEntry | undefined> {
const data = await this.redis.get(`ratelimit:${key}`);
return data ? JSON.parse(data) : undefined;
}
async set(key: string, entry: RateLimitEntry): Promise<void> {
const ttl = Math.ceil((entry.resetAt - Date.now()) / 1000);
await this.redis.setex(`ratelimit:${key}`, ttl, JSON.stringify(entry));
}
async increment(key: string, windowMs: number): Promise<RateLimitEntry> {
const now = Date.now();
const resetAt = now + windowMs;
const count = await this.redis.incr(`ratelimit:${key}`);
if (count === 1) {
await this.redis.pexpire(`ratelimit:${key}`, windowMs);
}
return { count, resetAt };
}
async reset(key: string): Promise<void> {
await this.redis.del(`ratelimit:${key}`);
}
}
const redisStore = new RedisRateLimitStore(new Redis());
server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 100,
windowMs: 60000,
store: redisStore,
}),
);- API abuse prevention
- Fair usage enforcement
- Cost control for expensive operations
- Protection against DDoS attacks
Provides flexible authentication support with multiple strategies.
type AuthConfig = {
/** Authentication type */
type: "bearer" | "api-key" | "basic" | "custom";
/** Token validation function */
validate: (token: string, ctx: ServerContext) => Promise<AuthResult | null>;
/** Header name for token */
headerName?: string;
/** Skip authentication for certain paths */
skipPaths?: string[];
/** Custom error message */
errorMessage?: string;
/** Token extractor for custom auth schemes */
extractToken?: (ctx: ServerContext) => string | null;
/** Skip auth for dev playground requests (default: true) */
skipDevPlayground?: boolean;
};
type AuthResult = {
id: string;
email?: string;
roles?: string[];
metadata?: Record<string, unknown>;
};import {
createAuthMiddleware,
createBearerAuthMiddleware,
createApiKeyAuthMiddleware,
createRoleMiddleware,
ApiKeyStore,
} from "@juspay/neurolink";
// Bearer token authentication
server.registerMiddleware(
createAuthMiddleware({
type: "bearer",
validate: async (token) => {
const user = await verifyJWT(token);
return user
? { id: user.id, email: user.email, roles: user.roles }
: null;
},
skipPaths: ["/api/health", "/api/ready"],
}),
);
// API key authentication
const apiKeyStore = new ApiKeyStore();
apiKeyStore.addKey("sk_live_abc123", { id: "user_1", roles: ["admin"] });
server.registerMiddleware(
createApiKeyAuthMiddleware(apiKeyStore, {
headerName: "x-api-key",
skipPaths: ["/api/health"],
}),
);
// Role-based access control (after authentication)
server.registerMiddleware(
createRoleMiddleware({
requiredRoles: ["admin"],
requireAll: false, // Any role matches
errorMessage: "Admin access required",
}),
);| Header | Auth Type | Description |
|---|---|---|
Authorization |
bearer, basic | Bearer <token> or Basic <base64> |
X-API-Key |
api-key | Raw API key value |
In non-production environments, requests with X-NeuroLink-Dev-Playground: true header bypass authentication and receive a default developer user context.
- Protecting API endpoints
- User identification and authorization
- Rate limiting by user
- Audit logging
Provides schema-based request validation for body, query, params, and headers.
type ValidationConfig = {
/** Schema for validating request body */
bodySchema?: ValidationSchema;
/** Schema for validating query parameters */
querySchema?: ValidationSchema;
/** Schema for validating path parameters */
paramsSchema?: ValidationSchema;
/** Schema for validating headers */
headersSchema?: ValidationSchema;
/** Custom validation function */
customValidator?: (ctx: ServerContext) => Promise<void>;
/** Skip validation for certain paths */
skipPaths?: string[];
/** Custom error formatter */
errorFormatter?: (errors: ValidationError[]) => unknown;
};
type ValidationSchema = {
required?: string[];
properties?: Record<string, PropertySchema>;
additionalProperties?: boolean;
};
type PropertySchema = {
type: "string" | "number" | "boolean" | "object" | "array";
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
minItems?: number;
maxItems?: number;
pattern?: string;
enum?: unknown[];
default?: unknown;
validate?: (value: unknown) => boolean | string;
};import {
createRequestValidationMiddleware,
createBodyValidationMiddleware,
createQueryValidationMiddleware,
CommonSchemas,
} from "@juspay/neurolink";
// Full validation
server.registerMiddleware(
createRequestValidationMiddleware({
bodySchema: {
required: ["input"],
properties: {
input: { type: "string", minLength: 1, maxLength: 10000 },
temperature: { type: "number", minimum: 0, maximum: 2 },
provider: { type: "string", enum: ["openai", "anthropic", "google"] },
},
},
querySchema: {
properties: {
stream: { type: "boolean" },
},
},
}),
);
// Body-only validation (convenience function)
server.registerMiddleware(
createBodyValidationMiddleware({
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", pattern: "^[^@]+@[^@]+\\.[^@]+$" },
},
}),
);
// Custom validation
server.registerMiddleware(
createRequestValidationMiddleware({
customValidator: async (ctx) => {
if (ctx.body?.startDate > ctx.body?.endDate) {
throw new ValidationError([
{
field: "dateRange",
message: "startDate must be before endDate",
},
]);
}
},
}),
);{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "body.input", "message": "input is required" },
{ "field": "body.temperature", "message": "Value must be at most 2" }
]
}
}Pre-built schemas for common validation patterns:
import { CommonSchemas } from "@juspay/neurolink";
// Use pagination schema
server.registerMiddleware(
createQueryValidationMiddleware(CommonSchemas.pagination),
);| Schema | Fields |
|---|---|
uuid |
UUID string format |
email |
Email string format |
pagination |
page, limit, offset |
sorting |
sortBy, sortOrder |
idParam |
Required id parameter |
dateRange |
startDate, endDate |
search |
q (query), fields (array) |
- Input sanitization and security
- API contract enforcement
- Early error detection
- Documentation generation (with OpenAPI)
Provides response caching with LRU eviction and configurable TTL.
type CacheConfig = {
/** Default TTL in milliseconds */
ttlMs: number;
/** Maximum cache size (default: 1000 entries) */
maxSize?: number;
/** Custom key generator */
keyGenerator?: (ctx: ServerContext) => string;
/** Methods to cache (default: ["GET"]) */
methods?: string[];
/** Paths to cache */
paths?: string[];
/** Paths to exclude from caching */
excludePaths?: string[];
/** Custom cache store */
store?: CacheStore;
/** Include query params in cache key (default: true) */
includeQuery?: boolean;
/** Custom TTL per path pattern */
ttlByPath?: Record<string, number>;
};import {
createCacheMiddleware,
InMemoryCacheStore,
ResponseCacheStore,
} from "@juspay/neurolink";
// Basic caching
server.registerMiddleware(
createCacheMiddleware({
ttlMs: 60 * 1000, // 1 minute
methods: ["GET"],
excludePaths: ["/api/health", "/api/agent/stream"],
}),
);
// With custom TTL per path
server.registerMiddleware(
createCacheMiddleware({
ttlMs: 60 * 1000,
ttlByPath: {
"/api/providers": 300 * 1000, // 5 minutes
"/api/models": 600 * 1000, // 10 minutes
},
}),
);
// Using ResponseCacheStore for synchronous access
const cacheStore = new ResponseCacheStore(1000, 60000);
cacheStore.set("GET:/api/data", { status: 200, data: [] });
const cached = cacheStore.get("GET:/api/data");| Header | Value | Description |
|---|---|---|
X-Cache |
HIT |
Response served from cache |
X-Cache |
MISS |
Response freshly generated |
X-Cache-Age |
45 |
Seconds since cached (only on HIT) |
Cache-Control |
max-age=60 |
Browser caching directive (on MISS) |
- Expensive operations (database queries, AI generation)
- Frequently requested static data
- Rate limit budget optimization
- Reducing latency for repeated requests
Middleware are executed in order based on their order property. Here's a recommended production setup:
import {
createTimingMiddleware,
createRequestIdMiddleware,
createErrorHandlingMiddleware,
createSecurityHeadersMiddleware,
createLoggingMiddleware,
createRateLimitMiddleware,
createAuthMiddleware,
createRequestValidationMiddleware,
createCacheMiddleware,
} from "@juspay/neurolink";
// Register middleware in recommended order
const middlewares = [
createTimingMiddleware(),
createRequestIdMiddleware(),
createErrorHandlingMiddleware({ includeStack: isDev }),
createSecurityHeadersMiddleware(),
createLoggingMiddleware({ skipPaths: ["/api/health"] }),
createRateLimitMiddleware({
maxRequests: 100,
windowMs: 60000,
skipPaths: ["/api/health"],
}),
createAuthMiddleware({
type: "bearer",
validate: verifyToken,
skipPaths: ["/api/health", "/api/docs"],
}),
createCacheMiddleware({
ttlMs: 60000,
methods: ["GET"],
excludePaths: ["/api/agent"],
}),
];
for (const middleware of middlewares) {
server.registerMiddleware(middleware);
}- Configuration Reference - Full server configuration options
- Security Best Practices - Authentication and authorization patterns
- Deployment Guide - Production deployment strategies
- Express Adapter - Express-specific middleware integration
- Fastify Adapter - Fastify-specific hooks and plugins