Express Adapter
The most popular Node.js web framework
Express is a minimal and flexible Node.js web framework that provides a robust set of features for building web applications and APIs. It has the largest ecosystem of middleware and is widely used in production.
Why Express?
| Feature | Benefit |
|---|---|
| Mature ecosystem | Thousands of middleware packages available |
| Well-documented | Extensive documentation and community resources |
| Familiar API | Most Node.js developers already know Express |
| Flexible | Unopinionated, adapt to any architecture |
| Production-proven | Powers millions of applications worldwide |
| Easy migration | Integrate NeuroLink into existing Express apps |
Express is ideal when you have an existing Express application or prefer its familiar middleware patterns.
CLI Usage
Start an Express server via CLI:
# Foreground mode
neurolink serve --framework express --port 3000
# Background mode
neurolink server start --framework express --port 3000
# Check routes
neurolink server routes
Quick Start
Installation
Express must be installed separately alongside NeuroLink:
npm install @juspay/neurolink express
Basic Usage
import { NeuroLink } from "@juspay/neurolink";
import { createServer } from "@juspay/neurolink";
const neurolink = new NeuroLink({
defaultProvider: "openai",
});
const server = await createServer(neurolink, {
framework: "express",
config: {
port: 3000,
basePath: "/api",
},
});
await server.initialize();
await server.start();
console.log("Server running on http://localhost:3000");
Test the Server
# Health check
curl http://localhost:3000/api/health
# Execute agent
curl -X POST http://localhost:3000/api/agent/execute \
-H "Content-Type: application/json" \
-d '{"input": "Hello, world!"}'
Accessing the Express App
For advanced customization, you can access the underlying Express application:
import { NeuroLink } from "@juspay/neurolink";
import { createServer } from "@juspay/neurolink";
import helmet from "helmet";
import morgan from "morgan";
const neurolink = new NeuroLink();
const server = await createServer(neurolink, {
framework: "express",
config: { port: 3000 },
});
// Get the underlying Express app
const app = server.getFrameworkInstance();
// Add Express middleware
app.use(helmet());
app.use(morgan("combined"));
// Add custom routes directly on Express
app.get("/custom", (req, res) => {
res.json({ message: "Custom route" });
});
// Add route groups with Express Router
import { Router } from "express";
const v2Router = Router();
v2Router.get("/status", (req, res) => res.json({ version: 2 }));
app.use("/v2", v2Router);
await server.initialize();
await server.start();
Configuration Options
Full Configuration Example
const server = await createServer(neurolink, {
framework: "express",
config: {
// Server settings
port: 3000,
host: "0.0.0.0",
basePath: "/api",
timeout: 30000, // 30 seconds
// CORS
cors: {
enabled: true,
origins: ["https://myapp.com", "https://staging.myapp.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
headers: ["Content-Type", "Authorization", "X-Request-ID"],
credentials: true,
maxAge: 86400, // 24 hours
},
// Rate limiting
rateLimit: {
enabled: true,
maxRequests: 100,
windowMs: 60000, // 1 minute
skipPaths: ["/api/health", "/api/ready"],
},
// Note: Rate-limited responses (HTTP 429) include a `Retry-After` header indicating seconds to wait.
// Body parsing
bodyParser: {
enabled: true,
maxSize: "10mb",
jsonLimit: "10mb",
},
// Logging
logging: {
enabled: true,
level: "info",
includeBody: false,
includeResponse: false,
},
// Documentation
enableSwagger: true,
enableMetrics: true,
},
});
Request body limits
bodyParser.jsonLimit bounds JSON bodies and bodyParser.maxSize bounds url-encoded forms. Both default to 10mb, and raising either one is honoured. Uploads (multipart/form-data, application/octet-stream, audio/*) are bounded by bodyParser.rawMaxSize, which defaults to maxSize when that is set and to 64mb otherwise.
A JSON or url-encoded form body over its limit is answered 413 with error.code set to PAYLOAD_TOO_LARGE and error.message set to request entity too large, before any middleware or route runs, and the connection is closed so the client stops sending. The body is not read past the limit: a declared Content-Length over the limit is refused without reading the body, and a body sent without one (chunked) is read only as far as the limit. For a compressed body (Content-Encoding: gzip, deflate, br or zstd) the limit applies to the inflated size, and reading stops once the body has inflated past it.
The bytes are still parsed by express.json() and express.urlencoded() (body-parser), so which requests are parsed, how charsets are decoded, strict JSON, and the qs parsing of forms are unchanged. Anything body-parser refuses other than a body that is too large (a malformed or scalar JSON body, an unsupported charset or Content-Encoding, too many form parameters) is answered 500 with error.code set to INTERNAL_ERROR, as before.
Middleware Integration
Using NeuroLink Middleware
import {
createServer,
createAuthMiddleware,
createRateLimitMiddleware,
createCacheMiddleware,
createRequestIdMiddleware,
createTimingMiddleware,
} from "@juspay/neurolink";
const server = await createServer(neurolink, {
framework: "express",
config: { port: 3000 },
});
// Add request ID to all requests
server.registerMiddleware(createRequestIdMiddleware());
// Add timing headers
server.registerMiddleware(createTimingMiddleware());
// Add authentication
server.registerMiddleware(
createAuthMiddleware({
type: "bearer",
validate: async (token) => {
const decoded = await verifyJWT(token);
return decoded ? { id: decoded.sub, roles: decoded.roles } : null;
},
skipPaths: ["/api/health", "/api/ready", "/api/version"],
}),
);
// Add rate limiting
server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 100,
windowMs: 60000,
keyGenerator: (ctx) => ctx.headers["x-api-key"] || ctx.ip,
}),
);
// Add response caching
server.registerMiddleware(
createCacheMiddleware({
ttlMs: 300000, // 5 minutes
methods: ["GET"],
excludePaths: ["/api/agent/execute", "/api/agent/stream"],
}),
);
// Note: Cached responses include `X-Cache: HIT` header. Fresh responses include `X-Cache: MISS`.
await server.initialize();
await server.start();
Using Express-Native Middleware
import helmet from "helmet";
import morgan from "morgan";
import compression from "compression";
import cors from "cors";
const server = await createServer(neurolink, { framework: "express" });
const app = server.getFrameworkInstance();
// Security headers
app.use(helmet());
// Logging
app.use(morgan("combined"));
// Compression
app.use(compression());
// Custom CORS configuration
app.use(
cors({
origin: (origin, callback) => {
// Dynamic origin checking
if (!origin || origin.endsWith(".myapp.com")) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"],
exposedHeaders: ["X-Request-Id", "X-Response-Time"],
maxAge: 86400,
credentials: true,
}),
);
await server.initialize();
await server.start();
Streaming Responses
Express supports streaming through Server-Sent Events (SSE):
// The /api/agent/stream endpoint is automatically set up
// It uses Server-Sent Events (SSE) for streaming
// Client-side usage:
// Note: EventSource only supports GET requests in browsers.
// Use query parameters for simple inputs:
const eventSource = new EventSource(
`/api/agent/stream?input=${encodeURIComponent("Write a story")}`,
);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "text-delta") {
console.log(data.content);
}
};
// For POST requests with SSE, use fetch with a readable stream:
async function streamWithPost() {
const response = await fetch("/api/agent/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ input: "Write a story" }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Parse SSE format: "data: {...}\n\n"
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = JSON.parse(line.slice(6));
if (data.type === "text-delta") {
console.log(data.content);
}
}
}
}
}
Custom Streaming Route
const app = server.getFrameworkInstance();
app.post("/api/custom-stream", async (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const result = await neurolink.stream({
input: { text: req.body.input },
});
for await (const chunk of result.stream) {
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
res.write("event: done\ndata: \n\n");
res.end();
});
Abort Signal Handling
The abort signal middleware allows detecting when clients disconnect during long-running requests. NeuroLink provides both a universal middleware and an Express-specific implementation.
Using Abort Signal Middleware
import {
createAbortSignalMiddleware,
createExpressAbortMiddleware,
} from "@juspay/neurolink";
// Option 1: Universal middleware (works with ServerContext)
const abortMiddleware = createAbortSignalMiddleware({
onAbort: (ctx) => {
console.log(`Request ${ctx.requestId} was aborted by client`);
},
timeout: 30000, // Optional request timeout
});
// Option 2: Express-specific middleware (lower-level)
app.use(createExpressAbortMiddleware());
// Access in route handler
app.get("/long-operation", async (req, res) => {
const { abortSignal } = res.locals;
// Check if aborted
if (abortSignal?.aborted) {
return res.status(499).json({ error: "Request cancelled" });
}
// Use with fetch or other AbortSignal-aware APIs
const response = await fetch(url, { signal: abortSignal });
});
Use Cases
The abort signal middleware is useful for:
- Long-running AI generation - Cancel generation when client disconnects
- Streaming responses - Stop producing chunks when client leaves
- Database queries - Cancel queries that support abort signals
- External API calls - Pass signal to fetch/axios for cancellation
Native Express Approach
For simpler cases, you can use Express's native socket events:
const app = server.getFrameworkInstance();
app.post("/api/long-running", async (req, res) => {
// Check if client disconnected
req.on("close", () => {
console.log("Client disconnected, cleaning up...");
// Cleanup resources
});
// Your long-running operation
const result = await neurolink.generate({
prompt: req.body.input,
});
res.json(result);
});
For streaming requests, the adapter automatically detects client disconnection and stops the stream to avoid unnecessary processing.
Error Handling
Custom Error Handler
const app = server.getFrameworkInstance();
// Custom error handling middleware (must be defined last)
app.use((err, req, res, next) => {
console.error("Error:", err);
// AI provider errors
if (err.message.includes("rate limit")) {
return res.status(429).json({
error: "Rate limit exceeded",
retryAfter: 60,
});
}
// Validation errors
if (err.name === "ValidationError") {
return res.status(400).json({
error: "Validation failed",
details: err.details,
});
}
// Default error response
res.status(500).json({
error: "Internal server error",
message: process.env.NODE_ENV === "development" ? err.message : undefined,
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({
error: "Not found",
path: req.path,
});
});
Integrating with Existing Express Apps
If you already have an Express application, you can integrate NeuroLink routes:
import express from "express";
import { NeuroLink } from "@juspay/neurolink";
import { createServer } from "@juspay/neurolink";
// Your existing Express app
const existingApp = express();
existingApp.use(express.json());
// Add your existing routes
existingApp.get("/", (req, res) => {
res.json({ message: "Welcome to my API" });
});
// Create NeuroLink server
// Note: basePath: "/" since Express mount path handles the prefix
const neurolink = new NeuroLink({ defaultProvider: "openai" });
const nlServer = await createServer(neurolink, {
framework: "express",
config: { basePath: "/" },
});
await nlServer.initialize();
// Mount NeuroLink routes on your existing app
const nlApp = nlServer.getFrameworkInstance();
existingApp.use("/ai", nlApp);
// Start your existing app
existingApp.listen(3000, () => {
console.log("Server running on http://localhost:3000");
console.log("AI endpoints available at /ai/*");
});
Testing
Unit Testing with Supertest
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import request from "supertest";
import { NeuroLink } from "@juspay/neurolink";
import { createServer } from "@juspay/neurolink";
describe("API Server", () => {
let server;
let app;
beforeAll(async () => {
const neurolink = new NeuroLink({ defaultProvider: "openai" });
server = await createServer(neurolink, { framework: "express" });
await server.initialize();
app = server.getFrameworkInstance();
});
afterAll(async () => {
await server.stop();
});
it("should return health status", async () => {
const res = await request(app).get("/api/health");
expect(res.status).toBe(200);
expect(res.body.status).toBe("ok");
});
it("should execute agent request", async () => {
const res = await request(app)
.post("/api/agent/execute")
.set("Content-Type", "application/json")
.send({ input: "Hello" });
expect(res.status).toBe(200);
expect(res.body.data).toBeDefined();
});
});
Production Checklist
- Configure environment variables securely
- Set appropriate CORS origins (not
*) - Enable rate limiting with reasonable limits
- Add authentication middleware
- Configure request timeouts
- Set body size limits
- Enable compression (gzip/brotli)
- Add security headers (helmet)
- Configure logging with appropriate level
- Set up health check monitoring
- Configure error tracking (Sentry, etc.)
- Use a process manager (PM2, systemd)
Related Documentation
- Server Adapters Overview - Getting started with server adapters
- Configuration Reference - Full configuration options
- Hono Adapter - Compare with Hono adapter
- Fastify Adapter - Compare with Fastify adapter
- Security Best Practices - Authentication patterns
Additional Resources
- Express Documentation - Official Express documentation
- Express Middleware - Popular middleware packages
- Express Security Best Practices - Security guidelines
Need Help? Join our GitHub Discussions or open an issue.