.agents/skills/express/SKILL.md
Express.js minimal Node.js web framework. Covers routing, middleware, error handling, and API design. Use when building Express APIs. USE WHEN: user mentions "Express", "express.js", "app.use", "middleware", "express Router", asks about "minimalist Node.js framework", "simple REST API", "lightweight web server", "Node.js middleware pattern" DO NOT USE FOR: Enterprise applications with DI - use `nestjs` instead, High-performance APIs - use `fastify` instead, Edge runtimes - use `hono` instead, Deno - use `oak` or `fresh` instead
npx skillsauth add d-subrahmanyam/deno-fresh-microservices expressInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:expressfor comprehensive documentation.
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
const app = express();
// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
// Routes
app.use('/api/users', userRoutes);
// Error handler (must be last)
app.use(errorHandler);
app.listen(3000);
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);
});
// Auth middleware
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);
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
};
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();
// Security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true },
}));
// CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many requests' },
});
app.use('/api', limiter);
// Slow down repeated requests
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 50,
delayMs: (hits) => hits * 100,
});
app.use('/api', speedLimiter);
// Prevent HTTP Parameter Pollution
app.use(hpp());
// Body parsing with limits
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
// Trust proxy (for rate limiting behind reverse proxy)
app.set('trust proxy', 1);
// Health endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Readiness endpoint (check dependencies)
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' });
}
});
// Liveness endpoint
app.get('/live', (req, res) => {
res.status(200).send('OK');
});
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);
| Metric | Alert Threshold | |--------|-----------------| | Request latency p99 | > 500ms | | Error rate (5xx) | > 1% | | Memory usage | > 80% | | Event loop lag | > 100ms | | Active handles | > 1000 |
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);
});
// Force shutdown after 30s
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 30000);
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
const errorHandler = (err, req, res, next) => {
// Log error
req.log.error({
err,
method: req.method,
url: req.url,
body: req.body,
});
// Don't leak error details in production
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 }),
});
};
// Must be last middleware
app.use(errorHandler);
graphql-expert for Apollo Server setupprisma-expert or sql-expert for ORM/query specifics| 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 |
| 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')) |
Note: For WebSocket functionality, consider using a dedicated WebSocket library alongside Express.
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 });
// Connection handling
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(`Client disconnected: ${userId}`);
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
});
server.listen(3000);
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');
}
},
});
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) => {
if (client !== exclude && client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
}
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));
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' }));
}
}
import { createClient } from 'redis';
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await pub.connect();
await sub.connect();
// Subscribe to channel
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));
}
});
});
// Publish from any server instance
function publishMessage(channel: string, message: object) {
pub.publish(channel, JSON.stringify(message));
}
development
Guidelines for building high-performance APIs with Fastify and TypeScript, covering validation, Prisma integration, and testing best practices
development
FastAPI modern Python web framework. Covers routing, Pydantic models, dependency injection, and async support. Use when building Python APIs. USE WHEN: user mentions "fastapi", "pydantic", "async python api", "python rest api", asks about "dependency injection python", "python openapi", "python swagger", "async endpoints", "python api validation", "fastapi middleware" DO NOT USE FOR: Django apps - use `django` instead, Flask apps - use `flask` instead, synchronous Python APIs without type hints, GraphQL-only APIs
tools
FastAPI integration testing specialist. Covers synchronous TestClient, async httpx AsyncClient, dependency injection overrides, auth testing (JWT, OAuth2, API keys), WebSocket testing, file uploads, background tasks, middleware testing, and HTTP mocking with respx, responses, and pytest-httpserver. USE WHEN: user mentions "FastAPI test", "TestClient", "httpx async test", "dependency override test", "respx mock", asks about testing FastAPI endpoints, authentication in tests, or HTTP client mocking. DO NOT USE FOR: Django - use `pytest-django`; pytest internals - use `pytest`; Container infrastructure - use `testcontainers-python`
development
Expert in FastAPI Python development with best practices for APIs and async operations