Skip to main content

Middleware Reference

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 Overview

MiddlewarePurposeOrder
createTimingMiddleware()Measures request duration0
createRequestIdMiddleware()Generates/propagates request IDs0
createErrorHandlingMiddleware()Centralized error catching and formatting1
createSecurityHeadersMiddleware()Adds security headers2
createLoggingMiddleware()Request/response logging3
createRateLimitMiddleware()Rate limiting5
createAbortSignalMiddleware()Client disconnection detection5
createCompressionMiddleware()Response compression signaling5
createAuthMiddleware()Authentication10
createRequestValidationMiddleware()Request body/query/params validation15
createCacheMiddleware()Response caching20
createMCPBodyAttachmentMiddleware()MCP SDK body compatibility10
createDeprecationMiddleware()RFC 8594 deprecation headers100

The order value determines execution sequence - lower numbers run first.


Timing Middleware

Measures request duration and adds timing headers to responses.

Usage

import { createTimingMiddleware } from "@juspay/neurolink";

server.registerMiddleware(createTimingMiddleware());

Headers Set

HeaderDescriptionExample
X-Response-TimeTotal request processing time in milliseconds45.23ms
Server-TimingStandard Server-Timing header for performance monitoringtotal;dur=45.23

When to Use

  • Always recommended for production servers
  • Essential for performance monitoring and debugging
  • Works with browser Developer Tools and APM systems

Request ID Middleware

Ensures every request has a unique identifier for tracing and debugging.

Configuration

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;
};

Usage

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()}`,
}),
);

Headers

HeaderDirectionDescription
X-Request-IDRequestPropagates existing ID from client (if present)
X-Request-IDResponseReturns request ID for client-side correlation

When to Use

  • Always recommended for production servers
  • Essential for distributed tracing
  • Enables log correlation across services
  • Helps with debugging and support tickets

Error Handling Middleware

Catches errors and formats them consistently across all routes.

Configuration

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;
};

Usage

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 Response Format

{
"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"
}
}

When to Use

  • Always recommended for production servers
  • Provides consistent error responses
  • Prevents leaking sensitive information in production
  • Enable stack traces only in development

Security Headers Middleware

Adds common security headers to protect against various web vulnerabilities.

Configuration

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>;
};

Usage

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",
},
}),
);

Headers Set

HeaderDefault ValueDescription
X-Frame-OptionsDENYPrevents clickjacking
X-Content-Type-OptionsnosniffPrevents MIME sniffing
Strict-Transport-Securitymax-age=31536000; includeSubDomainsEnforces HTTPS
Referrer-Policystrict-origin-when-cross-originControls referrer information
X-XSS-Protection1; mode=blockLegacy XSS protection
Content-Security-PolicyNot set by defaultContent security policy

When to Use

  • 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

Logging Middleware

Logs request and response information with configurable detail levels.

Configuration

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[];
};

Usage

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),
},
}),
);

Log Output

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 }

When to Use

  • 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

Compression Middleware

Signals compression preferences to adapters for response compression.

Configuration

type CompressionOptions = {
/** Minimum response size to compress in bytes (default: 1024) */
threshold?: number;
/** Content types to compress */
contentTypes?: string[];
};

Usage

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"],
}),
);

How It Works

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.

When to Use

  • 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)

Abort Signal Middleware

Provides client disconnection handling for long-running requests using AbortController.

Configuration

type AbortSignalMiddlewareOptions = {
/** Callback when abort is triggered */
onAbort?: (ctx: ServerContext) => void;
/** Request timeout in milliseconds */
timeout?: number;
};

Usage

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`);
},
}),
);

Using the Abort Signal in Route Handlers

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;
},
});

Express-Specific Middleware

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
});

When to Use

  • 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

MCP Body Attachment Middleware

Bridges the gap between Fastify's body parsing and the MCP SDK's body access pattern.

Usage

import { createMCPBodyAttachmentMiddleware } from "@juspay/neurolink";

// General middleware for any adapter
server.registerMiddleware(createMCPBodyAttachmentMiddleware());

Fastify-Specific Hook

For optimal Fastify integration, use the dedicated preHandler hook:

import { fastifyMCPBodyHook } from "@juspay/neurolink";

fastify.addHook("preHandler", fastifyMCPBodyHook);

How It Works

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.

When to Use

  • Required when using MCP routes with Fastify
  • Not needed for Hono, Express, or Koa adapters
  • Applied automatically by the Fastify adapter

Deprecation Middleware

Adds RFC 8594 compliant deprecation headers to responses for deprecated routes.

Configuration

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
};

Usage

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 }));

Headers Set

HeaderDescriptionExample
DeprecationRFC 8594 deprecation indicatortrue
SunsetWhen the endpoint will be removed (HTTP-date)Sun, 01 Jun 2025 00:00:00 GMT
LinkAlternative endpoint with rel="successor-version"</api/v2/users>; rel="successor-version"
X-Deprecation-NoticeHuman-readable deprecation messageUse /api/v2/users for improved performance

When to Use

  • API versioning migrations
  • Feature deprecation announcements
  • Gradual API evolution
  • Compliance with RFC 8594

Rate Limit Middleware

Provides configurable rate limiting with multiple algorithms.

Configuration

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;
};

Usage

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",
}),
);

Headers Set

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed per window100
X-RateLimit-RemainingRequests remaining in current window95
X-RateLimit-ResetUnix timestamp when the window resets1706746200
Retry-AfterSeconds to wait (only on 429 response)300

Custom Rate Limit Store (Redis)

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,
}),
);

When to Use

  • API abuse prevention
  • Fair usage enforcement
  • Cost control for expensive operations
  • Protection against DDoS attacks

Authentication Middleware

Provides flexible authentication support with multiple strategies.

Configuration

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>;
};

Usage

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",
}),
);

Headers Read

HeaderAuth TypeDescription
Authorizationbearer, basicBearer <token> or Basic <base64>
X-API-Keyapi-keyRaw API key value

Dev Playground Support

In non-production environments, requests with X-NeuroLink-Dev-Playground: true header bypass authentication and receive a default developer user context.

When to Use

  • Protecting API endpoints
  • User identification and authorization
  • Rate limiting by user
  • Audit logging

Request Validation Middleware

Provides schema-based request validation for body, query, params, and headers.

Configuration

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;
};

Usage

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 Response Format

{
"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" }
]
}
}

Common Schemas

Pre-built schemas for common validation patterns:

import { CommonSchemas } from "@juspay/neurolink";

// Use pagination schema
server.registerMiddleware(
createQueryValidationMiddleware(CommonSchemas.pagination),
);
SchemaFields
uuidUUID string format
emailEmail string format
paginationpage, limit, offset
sortingsortBy, sortOrder
idParamRequired id parameter
dateRangestartDate, endDate
searchq (query), fields (array)

When to Use

  • Input sanitization and security
  • API contract enforcement
  • Early error detection
  • Documentation generation (with OpenAPI)

Cache Middleware

Provides response caching with LRU eviction and configurable TTL.

Configuration

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>;
};

Usage

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");

Headers Set

HeaderValueDescription
X-CacheHITResponse served from cache
X-CacheMISSResponse freshly generated
X-Cache-Age45Seconds since cached (only on HIT)
Cache-Controlmax-age=60Browser caching directive (on MISS)

When to Use

  • Expensive operations (database queries, AI generation)
  • Frequently requested static data
  • Rate limit budget optimization
  • Reducing latency for repeated requests

Composing Middleware

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);
}

Next Steps