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

GitHub
安装命令
npx skhub add claude-dev-suite/express
Markdown
SKILL.md

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

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

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

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

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

// 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 Checks

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

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

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

  // Force shutdown after 30s
  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) => {
  // 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);

Checklist

  • Helmet security headers enabled
  • CORS properly configured
  • Rate limiting on API endpoints
  • Body size limits configured
  • Input validation (express-validator/joi)
  • HPP protection enabled
  • Structured logging (no console.log)
  • Health/readiness/liveness endpoints
  • Graceful shutdown handling
  • Error details hidden in production
  • HTTPS/TLS in production
  • Trust proxy configured (if behind LB)

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-PatternWhy It's BadCorrect Approach
Using app.get('*') before specific routesCatches all requests, routes unreachablePlace catch-all routes last
Not using next() in middlewareRequest hangs, no response sentAlways call next() or send response
Synchronous error throwing without try-catchCrashes serverWrap in try-catch, use next(err)
Using res.send() multiple times"Headers already sent" errorSend response once per request
Not setting trust proxy behind load balancerWrong client IP, rate limiting failsSet app.set('trust proxy', 1)
Parsing body without size limitsDoS vulnerabilitySet limit: '10kb' in body parsers
Using app.use(express.static()) without pathServes entire filesystemSpecify explicit public directory
Mixing callback and promise stylesInconsistent error handlingUse async/await consistently

Quick Troubleshooting

IssueLikely CauseSolution
"Cannot set headers after sent"Multiple res.send() callsEnsure only one response per request
Middleware not executingRegistered after routesMove app.use() before route definitions
404 for all routesRoutes defined after app.listen()Define routes before calling listen()
Request hangs indefinitelyMiddleware missing next()Add next() or send response
CORS errors despite cors middlewareMiddleware order issuePlace app.use(cors()) before routes
Rate limiting not workingBehind proxy without trust proxySet app.set('trust proxy', 1)
Body parser returns undefinedWrong content-type headerEnsure Content-Type: application/json
Static files not servingWrong path configurationUse 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 });

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

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) => {
    if (client !== exclude && client.readyState === WebSocket.OPEN) {
      client.send(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();

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

Reference Documentation

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

skills/backend-frameworks/express

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1