| name | express |
| description | | Use when this capability is needed. |
Express Core Knowledge
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: express for comprehensive documentation.
Basic Setup
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use('/api/users', userRoutes);
app.use(errorHandler);
app.listen(3000);
Route Patterns
import { Router } from 'express';
const router = Router();
router.get('/', async (req, res, next) => {
try {
const users = await db.users.findMany();
res.json(users);
} catch (err) {
next(err);
}
});
router.post('/', async (req, res, next) => {
try {
const user = await db.users.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
});
router.get('/:id', async (req, res, next) => {
const user = await db.users.find(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
Middleware Pattern
const authenticate = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });
try {
req.user = await verifyToken(token);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
};
router.get('/protected', authenticate, handler);
Error Handler
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
};
Production Readiness
Security Configuration
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import slowDown from 'express-slow-down';
import hpp from 'hpp';
const app = express();
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true },
}));
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
}));
limiter = ({
: * * ,
: ,
: ,
: ,
: { : },
});
app.(, limiter);
speedLimiter = ({
: * * ,
: ,
: hits * ,
});
app.(, speedLimiter);
app.(());
app.(express.({ : }));
app.(express.({ : , : }));
app.(, );
Health Checks
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.get('/ready', async (req, res) => {
try {
await db.query('SELECT 1');
res.json({ status: 'ready', database: 'connected' });
} catch (error) {
res.status(503).json({ status: 'not ready', database: 'disconnected' });
}
});
app.get('/live', (req, res) => {
res.status(200).send('OK');
});
Structured Logging
import pino from 'pino-http';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
redact: ['req.headers.authorization', 'req.body.password'],
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
query: req.query,
params: req.params,
}),
},
});
app.use(logger);
Monitoring Metrics
| Metric | Alert Threshold |
|---|
| Request latency p99 | > 500ms |
| Error rate (5xx) | > 1% |
| Memory usage | > 80% |
| Event loop lag | > 100ms |
| Active handles | > 1000 |
Graceful Shutdown
const server = app.listen(PORT);
const gracefulShutdown = async (signal: string) => {
console.log(`${signal} received, shutting down gracefully`);
server.close(async () => {
console.log('HTTP server closed');
await db.disconnect();
process.exit(0);
});
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 30000);
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
Error Handler (Production)
const errorHandler = (err, req, res, next) => {
req.log.error({
err,
method: req.method,
url: req.url,
body: req.body,
});
const statusCode = err.statusCode || err.status || 500;
const message = statusCode === 500 && process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message;
res.status(statusCode).json({
error: message,
...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
});
};
app.use(errorHandler);
Checklist
When NOT to Use This Skill
- Enterprise Architecture: Use NestJS for dependency injection, decorators, and modular design
- Maximum Performance: Use Fastify for schema-based validation and faster throughput
- Edge Runtimes: Use Hono for Cloudflare Workers, Vercel Edge, or edge-first design
- Type-Safe APIs: Consider tRPC with Express or use Fastify/NestJS for better TypeScript integration
- WebSocket Implementation: Use dedicated WebSocket skill (coming soon)
- GraphQL: Defer to
graphql-expert for Apollo Server setup
- Database Queries: Use
prisma-expert or sql-expert for ORM/query specifics
Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|---|
Using app.get('*') before specific routes | Catches all requests, routes unreachable | Place catch-all routes last |
Not using next() in middleware | Request hangs, no response sent | Always call next() or send response |
| Synchronous error throwing without try-catch | Crashes server | Wrap in try-catch, use next(err) |
Using res.send() multiple times | "Headers already sent" error | Send response once per request |
Not setting trust proxy behind load balancer | Wrong client IP, rate limiting fails | Set app.set('trust proxy', 1) |
| Parsing body without size limits | DoS vulnerability | Set limit: '10kb' in body parsers |
Using app.use(express.static()) without path | Serves entire filesystem | Specify explicit public directory |
| Mixing callback and promise styles | Inconsistent error handling | Use async/await consistently |
Quick Troubleshooting
| Issue | Likely Cause | Solution |
|---|
| "Cannot set headers after sent" | Multiple res.send() calls | Ensure only one response per request |
| Middleware not executing | Registered after routes | Move app.use() before route definitions |
| 404 for all routes | Routes defined after app.listen() | Define routes before calling listen() |
| Request hangs indefinitely | Middleware missing next() | Add next() or send response |
| CORS errors despite cors middleware | Middleware order issue | Place app.use(cors()) before routes |
| Rate limiting not working | Behind proxy without trust proxy | Set app.set('trust proxy', 1) |
| Body parser returns undefined | Wrong content-type header | Ensure Content-Type: application/json |
| Static files not serving | Wrong path configuration | Use absolute path: express.static(path.join(__dirname, 'public')) |
WebSocket Integration
Note: For WebSocket functionality, consider using a dedicated WebSocket library alongside Express.
Setup with ws
import express from 'express';
import { createServer } from 'http';
import { WebSocketServer, WebSocket } from 'ws';
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
wss.on('connection', (ws: WebSocket, req) => {
const userId = new URL(req.url!, `http://${req.headers.host}`).searchParams.get('userId');
console.log(`Client connected: ${userId}`);
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
handleMessage(ws, message);
});
ws.on('close', () => {
console.log();
});
ws.(, {
.(, error);
});
});
server.();
Authentication
import { WebSocketServer } from 'ws';
import jwt from 'jsonwebtoken';
const wss = new WebSocketServer({
server,
verifyClient: async ({ req }, done) => {
const token = req.url?.split('token=')[1];
if (!token) return done(false, 401, 'Unauthorized');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
(req as any).user = decoded;
done(true);
} catch {
done(false, 401, 'Invalid token');
}
},
});
Room Management
const rooms = new Map<string, Set<WebSocket>>();
function joinRoom(ws: WebSocket, roomId: string) {
if (!rooms.has(roomId)) {
rooms.set(roomId, new Set());
}
rooms.get(roomId)!.add(ws);
}
function leaveRoom(ws: WebSocket, roomId: string) {
rooms.get(roomId)?.delete(ws);
if (rooms.get(roomId)?.size === 0) {
rooms.delete(roomId);
}
}
function broadcastToRoom(roomId: string, message: object, exclude?: WebSocket) {
const clients = rooms.get(roomId);
if (!clients) return;
const data = JSON.stringify(message);
clients.forEach((client) => {
(client !== exclude && client. === .) {
client.(data);
}
});
}
Heartbeat & Connection Health
const HEARTBEAT_INTERVAL = 30000;
wss.on('connection', (ws: WebSocket) => {
(ws as any).isAlive = true;
ws.on('pong', () => {
(ws as any).isAlive = true;
});
});
const heartbeat = setInterval(() => {
wss.clients.forEach((ws) => {
if ((ws as any).isAlive === false) {
return ws.terminate();
}
(ws as any).isAlive = false;
ws.ping();
});
}, HEARTBEAT_INTERVAL);
wss.on('close', () => clearInterval(heartbeat));
Message Protocol
interface WSMessage {
type: string;
payload: unknown;
timestamp: number;
}
function handleMessage(ws: WebSocket, message: WSMessage) {
switch (message.type) {
case 'join_room':
joinRoom(ws, message.payload as string);
break;
case 'leave_room':
leaveRoom(ws, message.payload as string);
break;
case 'broadcast':
broadcastToRoom(
(message.payload as any).roomId,
(message.payload as any).data
);
break;
default:
ws.send(JSON.stringify({ type: 'error', payload: 'Unknown message type' }));
}
}
Scaling with Redis
import { createClient } from 'redis';
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await pub.connect();
await sub.connect();
await sub.subscribe('ws:broadcast', (message) => {
const data = JSON.parse(message);
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
});
function publishMessage(channel: string, message: object) {
pub.publish(channel, JSON.stringify(message));
}
Reference Documentation
Source: claude-dev-suite/claude-dev-suite — distributed by TomeVault.