Skip to main content

Express.js Integration Guide

Build production-ready AI APIs with Express.js and NeuroLink


Overview​

Express.js is the most popular Node.js web framework for building APIs. This guide shows how to integrate NeuroLink with Express to create scalable, production-ready AI endpoints with authentication, rate limiting, caching, and monitoring.

Key Features​

  • 🚀 RESTful APIs: Standard HTTP endpoints for AI operations
  • 🔒 Authentication: JWT, API keys, OAuth integration
  • ⚡ Rate Limiting: Protect against abuse
  • 💾 Response Caching: Redis-based caching
  • 📊 Monitoring: Prometheus metrics, logging
  • 🔄 Streaming: Server-Sent Events (SSE) for real-time responses

What You'll Build​

  • RESTful AI API with Express
  • Authentication and authorization
  • Rate-limited endpoints
  • Response caching with Redis
  • Streaming chat endpoints
  • Monitoring and analytics

Quick Start​

1. Initialize Project​

mkdir my-ai-api
cd my-ai-api
npm init -y
npm install express @juspay/neurolink dotenv
npm install -D @types/express @types/node typescript ts-node

2. Setup TypeScript​

// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}

3. Create Basic Server​

// src/index.ts
import express from "express";
import { NeuroLink } from "@juspay/neurolink";
import dotenv from "dotenv";

dotenv.config();

const app = express();
app.use(express.json());

// Initialize NeuroLink
const ai = new NeuroLink({
providers: [
{
name: "openai",
config: { apiKey: process.env.OPENAI_API_KEY },
},
{
name: "anthropic",
config: { apiKey: process.env.ANTHROPIC_API_KEY },
},
],
});

// Basic endpoint
app.post("/api/generate", async (req, res) => {
try {
const { prompt, provider = "openai", model = "gpt-4o-mini" } = req.body;

if (!prompt) {
return res.status(400).json({ error: "Prompt is required" });
}

const result = await ai.generate({
input: { text: prompt },
provider,
model,
});

res.json({
content: result.content,
usage: result.usage,
cost: result.cost,
});
} catch (error: any) {
console.error("AI Error:", error);
res.status(500).json({ error: error.message });
}
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`AI API server running on http://localhost:${PORT}`);
});

4. Environment Variables​

# .env
PORT=3000
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_AI_API_KEY=AIza...

5. Run Server​

npx ts-node src/index.ts

6. Test API​

curl -X POST http://localhost:3000/api/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Explain AI in one sentence"}'

Authentication​

API Key Authentication​

// src/middleware/auth.ts
import { Request, Response, NextFunction } from "express";

export function apiKeyAuth(req: Request, res: Response, next: NextFunction) {
const apiKey = req.headers["x-api-key"] as string;

if (!apiKey) {
return res.status(401).json({ error: "API key is required" });
}

if (apiKey !== process.env.API_SECRET) {
return res.status(401).json({ error: "Invalid API key" });
}

next();
}
// src/index.ts
import { apiKeyAuth } from "./middleware/auth";

// Protected endpoint
app.post("/api/generate", apiKeyAuth, async (req, res) => {
// ... AI generation
});

JWT Authentication​

// src/middleware/jwt-auth.ts
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";

type AuthRequest = Request & {
user?: any;
};

export function jwtAuth(req: AuthRequest, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace("Bearer ", "");

if (!token) {
return res.status(401).json({ error: "No token provided" });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: "Invalid token" });
}
}
// Login endpoint
app.post("/api/auth/login", async (req, res) => {
const { username, password } = req.body;

// Verify credentials (example)
if (username === "admin" && password === "password") {
const token = jwt.sign(
{ userId: "123", username },
process.env.JWT_SECRET!,
{ expiresIn: "24h" },
);

return res.json({ token });
}

res.status(401).json({ error: "Invalid credentials" });
});

// Protected endpoint
app.post("/api/generate", jwtAuth, async (req, res) => {
console.log("User:", req.user);
// ... AI generation
});

Rate Limiting​

Express Rate Limit​

npm install express-rate-limit
// src/middleware/rate-limit.ts
import rateLimit from "express-rate-limit";

// Basic rate limiting
export const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 requests per minute
message: "Too many requests, please try again later",
standardHeaders: true,
legacyHeaders: false,
});

// Stricter limit for expensive operations
export const strictLimiter = rateLimit({
windowMs: 60 * 1000,
max: 5, // 5 requests per minute
message: "Rate limit exceeded for this endpoint",
});
// src/index.ts
import { limiter, strictLimiter } from "./middleware/rate-limit";

// Apply to all routes
app.use("/api/", limiter);

// Stricter limit for expensive endpoint
app.post("/api/analyze", strictLimiter, async (req, res) => {
// ... expensive AI operation
});

Custom Rate Limiting with Redis​

npm install redis rate-limit-redis
// src/middleware/redis-rate-limit.ts
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import { createClient } from "redis";

const redisClient = createClient({
url: process.env.REDIS_URL || "redis://localhost:6379",
});

redisClient.connect();

export const redisLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: "rate_limit:",
}),
windowMs: 60 * 1000,
max: 20,
message: "Too many requests",
});

Response Caching​

Redis Caching Middleware​

npm install redis
// src/middleware/cache.ts
import { createClient } from "redis";
import { Request, Response, NextFunction } from "express";
import { createHash } from "crypto";

const redisClient = createClient({
url: process.env.REDIS_URL || "redis://localhost:6379",
});

redisClient.connect();

export function cache(ttl: number = 3600) {
return async (req: Request, res: Response, next: NextFunction) => {
// Generate cache key from request body
const cacheKey = `ai:${createHash("sha256")
.update(JSON.stringify(req.body))
.digest("hex")}`;

try {
// Check cache
const cached = await redisClient.get(cacheKey);

if (cached) {
console.log("Cache hit:", cacheKey);
return res.json(JSON.parse(cached));
}

// Cache miss - store response
const originalJson = res.json.bind(res);
res.json = function (body: any) {
redisClient.setEx(cacheKey, ttl, JSON.stringify(body));
return originalJson(body);
};

next();
} catch (error) {
console.error("Cache error:", error);
next();
}
};
}
// src/index.ts
import { cache } from "./middleware/cache";

// Cached endpoint (1 hour TTL)
app.post("/api/generate", cache(3600), async (req, res) => {
const result = await ai.generate({
input: { text: req.body.prompt },
});

res.json({ content: result.content });
});

Streaming Responses​

Server-Sent Events (SSE)​

// src/routes/stream.ts
import { Router } from "express";
import { ai } from "../ai";

const router = Router();

router.post("/stream", async (req, res) => {
const { prompt } = req.body;

// Set headers for SSE
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");

try {
for await (const chunk of ai.stream({
input: { text: prompt },
provider: "openai",
model: "gpt-4o-mini",
})) {
res.write(`data: ${JSON.stringify({ content: chunk.content })}\n\n`);
}

res.write("data: [DONE]\n\n");
res.end();
} catch (error: any) {
res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
res.end();
}
});

export default router;
// src/index.ts
import streamRouter from "./routes/stream";

app.use("/api", streamRouter);

WebSocket Streaming​

npm install ws @types/ws
// src/websocket.ts
import { WebSocketServer } from "ws";
import { Server } from "http";
import { ai } from "./ai";

export function setupWebSocket(server: Server) {
const wss = new WebSocketServer({ server, path: "/ws" });

wss.on("connection", (ws) => {
console.log("WebSocket client connected");

ws.on("message", async (data) => {
try {
const {
prompt,
provider = "openai",
model = "gpt-4o-mini",
} = JSON.parse(data.toString());

// Stream AI response over WebSocket
for await (const chunk of ai.stream({
input: { text: prompt },
provider,
model,
})) {
ws.send(JSON.stringify({ type: "chunk", content: chunk.content }));
}

ws.send(JSON.stringify({ type: "done" }));
} catch (error: any) {
ws.send(JSON.stringify({ type: "error", error: error.message }));
}
});

ws.on("close", () => {
console.log("WebSocket client disconnected");
});
});
}
// src/index.ts
import { createServer } from "http";
import { setupWebSocket } from "./websocket";

const server = createServer(app);
setupWebSocket(server);

server.listen(PORT, () => {
console.log(`Server with WebSocket running on port ${PORT}`);
});

Production Patterns​

Pattern 1: Multi-Endpoint AI API​

// src/routes/ai.ts
import { Router } from "express";
import { ai } from "../ai";
import { jwtAuth } from "../middleware/jwt-auth";
import { limiter } from "../middleware/rate-limit";
import { cache } from "../middleware/cache";

const router = Router();

// Text generation
router.post("/generate", jwtAuth, limiter, cache(3600), async (req, res) => {
try {
const { prompt, provider = "openai", model = "gpt-4o-mini" } = req.body;

const result = await ai.generate({
input: { text: prompt },
provider,
model,
});

res.json({
content: result.content,
usage: result.usage,
cost: result.cost,
});
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});

// Summarization
router.post("/summarize", jwtAuth, limiter, async (req, res) => {
try {
const { text } = req.body;

const result = await ai.generate({
input: { text: `Summarize this text:\n\n${text}` },
provider: "anthropic",
model: "claude-3-5-sonnet-20241022",
maxTokens: 200,
});

res.json({ summary: result.content });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});

// Translation
router.post("/translate", jwtAuth, limiter, cache(86400), async (req, res) => {
try {
const { text, targetLanguage } = req.body;

const result = await ai.generate({
input: { text: `Translate to ${targetLanguage}: ${text}` },
provider: "google-ai",
model: "gemini-2.0-flash",
});

res.json({ translation: result.content });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});

// Code generation
router.post("/code", jwtAuth, limiter, async (req, res) => {
try {
const { description, language } = req.body;

const result = await ai.generate({
input: { text: `Write ${language} code: ${description}` },
provider: "anthropic",
model: "claude-3-5-sonnet-20241022",
});

res.json({ code: result.content });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});

export default router;

Pattern 2: Usage Tracking​

// src/middleware/usage-tracking.ts
import { Request, Response, NextFunction } from "express";
import { prisma } from "../db";

type AuthRequest = Request & {
user?: any;
};

export function trackUsage(
req: AuthRequest,
res: Response,
next: NextFunction,
) {
const originalJson = res.json.bind(res);

res.json = async function (body: any) {
// Track AI usage in database
if (req.user && body.usage) {
await prisma.aiUsage.create({
data: {
userId: req.user.userId,
provider: body.provider || "unknown",
model: body.model || "unknown",
tokens: body.usage.totalTokens,
cost: body.cost || 0,
endpoint: req.path,
timestamp: new Date(),
},
});
}

return originalJson(body);
};

next();
}
// src/routes/ai.ts
import { trackUsage } from "../middleware/usage-tracking";

router.post("/generate", jwtAuth, limiter, trackUsage, async (req, res) => {
// ... AI generation
});

// Get user's usage stats
router.get("/usage", jwtAuth, async (req, res) => {
const stats = await prisma.aiUsage.aggregate({
where: { userId: req.user.userId },
_sum: { tokens: true, cost: true },
_count: true,
});

res.json({
totalRequests: stats._count,
totalTokens: stats._sum.tokens || 0,
totalCost: stats._sum.cost || 0,
});
});

Pattern 3: Error Handling​

// src/middleware/error-handler.ts
import { Request, Response, NextFunction } from "express";

export function errorHandler(
error: Error,
req: Request,
res: Response,
next: NextFunction,
) {
console.error("Error:", error);

// AI provider errors
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: "Rate limit exceeded",
message: "Please try again later",
});
}

if (error.message.includes("quota")) {
return res.status(503).json({
error: "Service quota exceeded",
message: "AI service temporarily unavailable",
});
}

if (error.message.includes("authentication")) {
return res.status(401).json({
error: "Authentication failed",
message: "Invalid API credentials",
});
}

// Generic error
res.status(500).json({
error: "Internal server error",
message:
process.env.NODE_ENV === "development"
? error.message
: "Something went wrong",
});
}
// src/index.ts
import { errorHandler } from "./middleware/error-handler";

// ... routes

// Error handler must be last
app.use(errorHandler);

Monitoring & Logging​

Prometheus Metrics​

npm install prom-client
// src/metrics.ts
import { Registry, Counter, Histogram } from "prom-client";

export const register = new Registry();

export const httpRequestsTotal = new Counter({
name: "http_requests_total",
help: "Total HTTP requests",
labelNames: ["method", "route", "status"],
registers: [register],
});

export const aiRequestsTotal = new Counter({
name: "ai_requests_total",
help: "Total AI requests",
labelNames: ["provider", "model"],
registers: [register],
});

export const aiRequestDuration = new Histogram({
name: "ai_request_duration_seconds",
help: "AI request duration",
labelNames: ["provider", "model"],
registers: [register],
});

export const aiTokensUsed = new Counter({
name: "ai_tokens_used_total",
help: "Total AI tokens used",
labelNames: ["provider", "model"],
registers: [register],
});

export const aiCostTotal = new Counter({
name: "ai_cost_total",
help: "Total AI cost in USD",
labelNames: ["provider", "model"],
registers: [register],
});
// src/index.ts
import { register, httpRequestsTotal, aiRequestsTotal } from "./metrics";

// Metrics endpoint
app.get("/metrics", async (req, res) => {
res.setHeader("Content-Type", register.contentType);
res.send(await register.metrics());
});

// Track HTTP requests
app.use((req, res, next) => {
res.on("finish", () => {
httpRequestsTotal.inc({
method: req.method,
route: req.route?.path || req.path,
status: res.statusCode,
});
});
next();
});

Request Logging​

npm install winston
// src/logger.ts
import winston from "winston";

export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
transports: [
new winston.transports.File({ filename: "error.log", level: "error" }),
new winston.transports.File({ filename: "combined.log" }),
new winston.transports.Console({
format: winston.format.simple(),
}),
],
});
// src/index.ts
import { logger } from "./logger";

app.post("/api/generate", async (req, res) => {
logger.info("AI request received", {
userId: req.user?.userId,
prompt: req.body.prompt.substring(0, 50),
});

try {
const result = await ai.generate({
/* ... */
});

logger.info("AI request completed", {
userId: req.user?.userId,
provider: result.provider,
tokens: result.usage.totalTokens,
cost: result.cost,
});

res.json(result);
} catch (error: any) {
logger.error("AI request failed", {
userId: req.user?.userId,
error: error.message,
});

res.status(500).json({ error: error.message });
}
});

Best Practices​

1. ✅ Use Middleware for Cross-Cutting Concerns​

// ✅ Good: Compose middleware
app.post(
"/api/generate",
jwtAuth, // Authentication
limiter, // Rate limiting
cache(3600), // Caching
trackUsage, // Analytics
async (req, res) => {
// Business logic
},
);

2. ✅ Implement Proper Error Handling​

// ✅ Good: Centralized error handling
app.use(errorHandler);

3. ✅ Cache Expensive Operations​

// ✅ Good: Cache AI responses
app.post("/api/generate", cache(3600), async (req, res) => {
// ...
});

4. ✅ Monitor Performance​

// ✅ Good: Track metrics
aiRequestDuration.observe({ provider, model }, duration);
aiTokensUsed.inc({ provider, model }, tokens);

5. ✅ Validate Inputs​

npm install express-validator
import { body, validationResult } from "express-validator";

app.post(
"/api/generate",
body("prompt").isString().isLength({ min: 1, max: 10000 }),
body("provider").optional().isIn(["openai", "anthropic", "google-ai"]),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

// ... AI generation
},
);

Deployment​

Docker Deployment​

# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["node", "dist/index.js"]
# docker-compose.yml
version: "3.8"

services:
api:
build: .
ports:
- "3000:3000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- REDIS_URL=redis://redis:6379
depends_on:
- redis

redis:
image: redis:7-alpine
ports:
- "6379:6379"

Production Checklist​

  • Environment variables configured
  • Rate limiting enabled
  • Authentication implemented
  • Error handling comprehensive
  • Logging configured
  • Metrics endpoint exposed
  • Caching enabled
  • HTTPS configured
  • CORS configured properly
  • Input validation in place


Additional Resources​


Need Help? Join our GitHub Discussions or open an issue.