express-production — скилл Claude Code: что делает и как установить
Скилл внедряет в проект готовую продакшн-архитектуру на Express.js. Пригодится при настройке надёжных REST API с защитными заголовками, логированием и корректным завершением процессов.
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a claude-codeЧто делает express-production
Скилл даёт чёткие паттерны построения бэкенда на Express. Он организует файловую структуру проекта с разделением на контроллеры, роуты и сервисы. В код закладывается фабрика приложения в src/app.js и отдельная точка входа src/server.js.
Архитектура строго задаёт порядок вызова middleware. Сначала подключаются защитные заголовки через helmet, затем CORS и лимитер запросов express-rate-limit. Только после них запускаются парсеры тела запроса, сжатие compression и логирование в morgan или winston. Для изоляции сбоев предложен класс AppError и асинхронная обёртка catchAsync.
Запуск в проде рассчитан на кластеризацию через PM2 с готовым файлом ecosystem.config.js. Перехват сигнала SIGTERM останавливает сервер без сброса текущих соединений. Если в проекте не установлены библиотеки helmet, cors и dotenv, код middleware упадёт с ошибкой импорта.
Какие задачи решает
- выстроить правильный порядок подключения middleware с защитой от атак
- настроить безопасный graceful shutdown сервера по сигналу
SIGTERM - организовать централизованную обработку ошибок с классом
AppError - подготовить конфигурацию PM2 для кластеризации и деплоя без даунтайма
Примеры запросов агенту
- Собери базовый каркас Express-приложения с разделением на
app.jsиserver.js - Настрой цепочку middleware с
helmet,corsиexpress-rate-limitв правильном порядке - Добавь корректную обработку
SIGTERMдля плавного завершения работы сервера
Как установить express-production
1Через Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a claude-code2Вручную: положить файлы
~/.claude/skills/express-production/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/express-production/
1Через Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a codex2Вручную: положить файлы
.agents/skills/express-production/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a cursor2Вручную: положить файлы
.cursor/skills/express-production/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install bobmatnyc/claude-mpm-skills express-production2Вручную: положить файлы
.github/skills/express-production/SKILL.md1Через Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a gemini-cli2Командой платформы
gemini skills install https://github.com/bobmatnyc/claude-mpm-skills --path toolchains/javascript/frameworks/express-productionВручную: положить файлы
.devin/skills/express-production/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о express-production
Как установить express-production в Claude Code?
Выполните команду npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a claude-code. Скилл автоматически загрузится в конфигурацию окружения.
Что делает скилл express-production?
Он направляет агента при разработке боевых Express-приложений. Скилл регламентирует структуру папок, цепочки middleware, централизованные ошибки и параметры кластеризации.
Какие пакеты требуются для запуска готового сервера?
Нужны зависимости helmet, cors, express-rate-limit и dotenv. Для логирования и запуска сервиса в продакшне понадобятся morgan, winston и PM2.
Исходный текст
SKILL.md · MIT
---
name: express-production
description: Production-ready Express.js development covering middleware architecture, error handling, security hardening, testing strategies, and deployment patterns
user-invocable: false
disable-model-invocation: true
skill_version: 2.0.0
updated_at: 2025-12-03T00:00:00Z
tags: [express, nodejs, production, middleware, security, testing, deployment, backend]
progressive_disclosure:
entry_point:
summary: "Production-ready Express.js framework with comprehensive middleware, security, testing, and deployment patterns"
when_to_use: "Building production REST APIs, microservices, web applications requiring robust middleware architecture, security hardening, and comprehensive testing"
quick_start: "1. npm install express 2. Create app.js with middleware stack 3. Implement error handling 4. Add security middleware 5. Deploy with PM2"
references:
- references/middleware-patterns.md
- references/security-hardening.md
- references/testing-strategies.md
- references/production-deployment.md
context_limit: 800
---
# Express.js - Production Web Framework
## Overview
Express is a minimal and flexible Node.js web application framework providing a robust set of features for web and mobile applications. This skill covers **production-ready** Express development including middleware architecture, structured error handling, security hardening, comprehensive testing, and deployment strategies.
**Key Features**:
- Flexible middleware architecture with composition patterns
- Centralized error handling with async support
- Security hardening (Helmet, CORS, rate limiting, input validation)
- Comprehensive testing with Supertest
- Production deployment with PM2 clustering
- Environment-based configuration
- Structured logging and monitoring
- Graceful shutdown patterns
- Zero-downtime deployments
**Installation**:
```bash
# Basic Express
npm install express
# Production stack
npm install express helmet cors express-rate-limit express-validator
npm install morgan winston compression
npm install dotenv
# Development tools
npm install -D nodemon supertest jest
# Optional: Database and auth
npm install mongoose jsonwebtoken bcrypt
```
## When to Use This Skill
Use this comprehensive Express skill when:
- Building production REST APIs
- Creating microservices architectures
- Implementing secure web applications
- Need flexible middleware composition
- Require comprehensive error handling
- Building systems requiring extensive testing
- Deploying high-availability services
- Need granular control over request/response lifecycle
**Express vs Other Frameworks**:
- **Express**: Maximum flexibility, unopinionated, extensive ecosystem
- **Fastify**: Performance-focused, schema-based validation
- **Koa**: Modern async/await, minimalist
- **NestJS**: TypeScript-first, opinionated, enterprise patterns
## Quick Start
### Minimal Express Server
```javascript
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.get('/', (req, res) => {
res.json({ message: 'Hello World' });
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
// Error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
// Start server
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, closing server...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
```
**Run Development Server**:
```bash
# Install nodemon
npm install -D nodemon
# Run with nodemon
npx nodemon server.js
# Or add to package.json
npm run dev
```
### Production-Ready Server Structure
```
project/
├── src/
│ ├── app.js # Express app factory
│ ├── server.js # Server entry point
│ ├── config/
│ │ ├── index.js # Configuration management
│ │ └── logger.js # Winston logger setup
│ ├── middleware/
│ │ ├── errorHandler.js # Centralized error handling
│ │ ├── validation.js # Input validation
│ │ ├── auth.js # Authentication middleware
│ │ └── rateLimiter.js # Rate limiting
│ ├── routes/
│ │ ├── index.js # Route aggregator
│ │ ├── users.js # User routes
│ │ └── api/ # API versioning
│ ├── controllers/
│ │ ├── userController.js
│ │ └── authController.js
│ ├── models/ # Data models
│ ├── services/ # Business logic
│ ├── utils/
│ │ ├── AppError.js # Custom error class
│ │ └── catchAsync.js # Async wrapper
│ └── tests/
│ ├── unit/
│ └── integration/
├── ecosystem.config.js # PM2 configuration
├── .env.example # Environment template
├── nodemon.json # Nodemon config
└── package.json
```
## Middleware Architecture
### Understanding Middleware
Middleware functions are functions that have access to the request object (`req`), response object (`res`), and the next middleware function (`next`).
**Middleware Types**:
1. **Application-level**: `app.use()` or `app.METHOD()`
2. **Router-level**: `router.use()` or `router.METHOD()`
3. **Error-handling**: Four parameters `(err, req, res, next)`
4. **Built-in**: `express.json()`, `express.static()`
5. **Third-party**: `helmet`, `cors`, `morgan`
### Proper Middleware Order
✅ **Correct Order**:
```javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const app = express();
// 1. Security headers (FIRST)
app.use(helmet());
// 2. CORS configuration
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// 3. Rate limiting (before parsing)
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);
// 4. Request parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 5. Compression
app.use(compression());
// 6. Logging
if (process.env.NODE_ENV !== 'production') {
app.use(morgan('dev'));
} else {
app.use(morgan('combined'));
}
// 7. Static files (if needed)
app.use(express.static('public'));
// 8. Custom middleware
app.use(require('./middleware/requestId'));
app.use(require('./middleware/timing'));
// 9. Routes
app.use('/api/v1/users', require('./routes/users'));
app.use('/api/v1/posts', require('./routes/posts'));
// 10. 404 handler (after all routes)
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// 11. Error handling (LAST)
app.use(require('./middleware/errorHandler'));
```
❌ **Wrong Order**:
```javascript
// DON'T: Routes before security
app.use('/api/users', userRoutes); // Routes first
app.use(helmet()); // Security too late!
// DON'T: Error handler before routes
app.use(errorHandler); // Error handler first
app.use('/api/users', userRoutes); // Routes won't be caught
// DON'T: Parsing after routes
app.use('/api/users', userRoutes);
app.use(express.json()); // Too late to parse!
```
### Custom Middleware Patterns
**Request ID Middleware**:
```javascript
// middleware/requestId.js
const { v4: uuidv4 } = require('uuid');
module.exports = function requestId(req, res, next) {
req.id = req.headers['x-request-id'] || uuidv4();
res.setHeader('X-Request-ID', req.id);
next();
};
```
**Request Timing Middleware**:
```javascript
// middleware/timing.js
module.exports = function timing(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${duration}ms`);
});
next();
};
```
**Authentication Middleware**:
```javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
const AppError = require('../utils/AppError');
exports.authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return next(new AppError('No token provided', 401));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
next(new AppError('Invalid token', 401));
}
};
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return next(new AppError('Not authenticated', 401));
}
if (!roles.includes(req.user.role)) {
return next(new AppError('Insufficient permissions', 403));
}
next();
};
};
```
**Usage**:
```javascript
const { authenticate, authorize } = require('./middleware/auth');
// Public route
app.get('/api/posts', getPosts);
// Authenticated route
app.get('/api/profile', authenticate, getProfile);
// Role-based authorization
app.delete('/api/users/:id',
authenticate,
authorize('admin', 'moderator'),
deleteUser
);
```
### Async Middleware
✅ **Correct Async Handling**:
```javascript
// utils/catchAsync.js
module.exports = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
// Usage
const catchAsync = require('../utils/catchAsync');
app.get('/users', catchAsync(async (req, res) => {
const users = await User.find();
res.json({ users });
}));
```
❌ **Wrong: No Error Handling**:
```javascript
// DON'T: Async without catch
app.get('/users', async (req, res) => {
const users = await User.find(); // Unhandled rejection!
res.json({ users });
});
```
### Middleware Composition
**Compose Multiple Middleware**:
```javascript
// middleware/compose.js
const compose = (...middleware) => {
return (req, res, next) => {
let index = 0;
const dispatch = (i) => {
if (i >= middleware.length) return next();
const fn = middleware[i];
try {
fn(req, res, () => dispatch(i + 1));
} catch (err) {
next(err);
}
};
dispatch(0);
};
};
// Usage
const adminOnly = compose(
authenticate,
authorize('admin'),
validateRequest
);
app.delete('/api/users/:id', adminOnly, deleteUser);
```
**Conditional Middleware**:
```javascript
// Apply middleware conditionally
const conditionalMiddleware = (condition, middleware) => {
return (req, res, next) => {
if (condition(req)) {
return middleware(req, res, next);
}
next();
};
};
// Only log in development
app.use(conditionalMiddleware(
(req) => process.env.NODE_ENV === 'development',
morgan('dev')
));
```
## Structured Error Handling
### Custom Error Classes
```javascript
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = AppError;
```
**Error Hierarchy**:
```javascript
// utils/errors.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
class ValidationError extends AppError {
constructor(message, errors = []) {
super(message, 400);
this.errors = errors;
}
}
class AuthenticationError extends AppError {
constructor(message = 'Authentication required') {
super(message, 401);
}
}
class AuthorizationError extends AppError {
constructor(message = 'Insufficient permissions') {
super(message, 403);
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404);
}
}
class ConflictError extends AppError {
constructor(message = 'Resource conflict') {
super(message, 409);
}
}
module.exports = {
AppError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ConflictError
};
```
### Centralized Error Handler
```javascript
// middleware/errorHandler.js
const logger = require('../config/logger');
function errorHandler(err, req, res, next) {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
// Log error
logger.error({
message: err.message,
statusCode: err.statusCode,
stack: err.stack,
path: req.path,
method: req.method,
ip: req.ip,
userId: req.user?.id
});
// Development: send full error
if (process.env.NODE_ENV === 'development') {
return res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
}
// Production: sanitize errors
if (err.isOperational) {
// Operational, trusted error: send to client
return res.status(err.statusCode).json({
status: err.status,
message: err.message,
...(err.errors && { errors: err.errors })
});
}
// Programming or unknown error: don't leak details
console.error('ERROR 💥', err);
return res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
}
module.exports = errorHandler;
```
### Handling Specific Error Types
```javascript
// middleware/errorHandler.js (extended)
function handleCastError(err) {
const message = `Invalid ${err.path}: ${err.value}`;
return new AppError(message, 400);
}
function handleDuplicateFields(err) {
const field = Object.keys(err.keyValue)[0];
const message = `Duplicate field value: ${field}. Please use another value`;
return new AppError(message, 400);
}
function handleValidationError(err) {
const errors = Object.values(err.errors).map(el => el.message);
const message = `Invalid input data. ${errors.join('. ')}`;
return new AppError(message, 400);
}
function handleJWTError() {
return new AppError('Invalid token. Please log in again', 401);
}
function handleJWTExpiredError() {
return new AppError('Your token has expired. Please log in again', 401);
}
module.exports = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Mongoose bad ObjectId
if (err.name === 'CastError') error = handleCastError(error);
// Mongoose duplicate key
if (err.code === 11000) error = handleDuplicateFields(error);
// Mongoose validation error
if (err.name === 'ValidationError') error = handleValidationError(error);
// JWT errors
if (err.name === 'JsonWebTokenError') error = handleJWTError();
if (err.name === 'TokenExpiredError') error = handleJWTExpiredError();
// Send response
sendErrorResponse(error, req, res);
};
```
### Async Error Handling
```javascript
// utils/catchAsync.js
const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
module.exports = catchAsync;
// Usage in controllers
const catchAsync = require('../utils/catchAsync');
const User = require('../models/User');
const { NotFoundError } = require('../utils/errors');
exports.getUser = catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new NotFoundError('User'));
}
res.json({ user });
});
exports.createUser = catchAsync(async (req, res, next) => {
const user = await User.create(req.body);
res.status(201).json({ user });
});
```
### Unhandled Rejections
```javascript
// server.js
const app = require('./app');
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
console.error('UNHANDLED REJECTION! 💥 Shutting down...');
console.error(err.name, err.message);
server.close(() => {
process.exit(1);
});
});
// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('UNCAUGHT EXCEPTION! 💥 Shutting down...');
console.error(err.name, err.message);
process.exit(1);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('👋 SIGTERM RECEIVED. Shutting down gracefully');
server.close(() => {
console.log('💥 Process terminated!');
});
});
```
## Security Hardening
### Helmet.js Configuration
```javascript
// config/security.js
const helmet = require('helmet');
const securityConfig = helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
// Strict Transport Security
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
},
// X-Frame-Options
frameguard: {
action: 'deny'
},
// X-Content-Type-Options
noSniff: true,
// X-XSS-Protection
xssFilter: true,
// Referrer-Policy
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
}
});
module.exports = securityConfig;
```
**Usage**:
```javascript
// app.js
const securityConfig = require('./config/security');
app.use(securityConfig);
```
### CORS Configuration
```javascript
// config/cors.js
const cors = require('cors');
const whitelist = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
const corsOptions = {
origin: function (origin, callback) {
// Allow requests with no origin (mobile apps, Postman)
if (!origin) return callback(null, true);
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
maxAge: 86400 // 24 hours
};
module.exports = cors(corsOptions);
```
### Rate Limiting
```javascript
// middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
// Redis client for distributed rate limiting
const redisClient = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
});
// General rate limiter
exports.generalLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:general:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later',
standardHeaders: true, // Return rate limit info in RateLimit-* headers
legacyHeaders: false // Disable X-RateLimit-* headers
});
// Strict rate limiter for auth endpoints
exports.authLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:auth:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 login attempts per windowMs
message: 'Too many login attempts, please try again later',
skipSuccessfulRequests: true // Don't count successful requests
});
// API key limiter (higher limits for authenticated users)
exports.apiKeyLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 1000,
keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
skip: (req) => !req.headers['x-api-key']
});
```
**Usage**:
```javascript
const { generalLimiter, authLimiter } = require('./middleware/rateLimiter');
// Apply to all routes
app.use('/api/', generalLimiter);
// Strict limiting for auth
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);
```
### Input Validation and Sanitization
```javascript
// middleware/validation.js
const { body, param, query, validationResult } = require('express-validator');
const { ValidationError } = require('../utils/errors');
// Validation middleware
exports.validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const extractedErrors = errors.array().map(err => ({
field: err.param,
message: err.msg,
value: err.value
}));
return next(new ValidationError('Validation failed', extractedErrors));
}
next();
};
// User validation rules
exports.createUserRules = [
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Must be a valid email'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('Password must contain uppercase, lowercase, and number'),
body('name')
.trim()
.notEmpty()
.withMessage('Name is required')
.isLength({ max: 100 })
.withMessage('Name too long')
.escape(), // XSS protection
body('age')
.optional()
.isInt({ min: 0, max: 150 })
.withMessage('Age must be between 0 and 150')
];
exports.updateUserRules = [
param('id')
.isMongoId()
.withMessage('Invalid user ID'),
body('email')
.optional()
.isEmail()
.normalizeEmail(),
body('name')
.optional()
.trim()
.notEmpty()
.escape()
];
// Usage
const { createUserRules, validate } = require('./middleware/validation');
app.post('/api/users', createUserRules, validate, createUser);
```
### SQL Injection Prevention
```javascript
// DON'T: String concatenation
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`; // Vulnerable!
// DO: Parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
connection.query(query, [req.body.email], (err, results) => {
// Safe from SQL injection
});
// DO: ORM/Query Builder
const user = await User.findOne({ email: req.body.email }); // Mongoose
const user = await db('users').where('email', req.body.email).first(); // Knex
```
### XSS Protection
```javascript
// Install: npm install xss-clean
const xss = require('xss-clean');
// Apply XSS sanitization
app.use(xss());
// Additional: HTML escaping in templates
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
```
### Environment Variable Security
```javascript
// config/index.js
require('dotenv').config();
const requiredEnvVars = [
'NODE_ENV',
'PORT',
'DATABASE_URL',
'JWT_SECRET',
'REDIS_HOST'
];
// Validate required environment variables
requiredEnvVars.forEach((envVar) => {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
});
// Validate JWT_SECRET strength
if (process.env.JWT_SECRET.length < 32) {
throw new Error('JWT_SECRET must be at least 32 characters');
}
module.exports = {
env: process.env.NODE_ENV,
port: parseInt(process.env.PORT, 10),
database: {
url: process.env.DATABASE_URL
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
},
redis: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT, 10) || 6379
}
};
```
## Testing with Supertest
### Test Setup
```javascript
// tests/setup.js
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
let mongoServer;
// Setup before all tests
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const mongoUri = mongoServer.getUri();
await mongoose.connect(mongoUri);
});
// Cleanup after each test
afterEach(async () => {
const collections = mongoose.connection.collections;
for (const key in collections) {
await collections[key].deleteMany();
}
});
// Teardown after all tests
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});
```
### Integration Testing
```javascript
// tests/integration/users.test.js
const request = require('supertest');
const app = require('../../src/app');
const User = require('../../src/models/User');
describe('User API', () => {
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
email: '[email protected]',
name: 'Test User',
password: 'Password123'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toHaveProperty('user');
expect(response.body.user.email).toBe(userData.email);
expect(response.body.user).not.toHaveProperty('password');
});
it('should return 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'invalid-email',
name: 'Test User',
password: 'Password123'
})
.expect(400);
expect(response.body).toHaveProperty('errors');
});
it('should return 409 for duplicate email', async () => {
const userData = {
email: '[email protected]',
name: 'Test User',
password: 'Password123'
};
// Create first user
await User.create(userData);
// Try to create duplicate
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(409);
expect(response.body.message).toMatch(/duplicate/i);
});
});
describe('GET /api/users/:id', () => {
it('should get user by ID', async () => {
const user = await User.create({
email: '[email protected]',
name: 'Get User',
password: 'Password123'
});
const response = await request(app)
.get(`/api/users/${user._id}`)
.expect(200);
expect(response.body.user._id).toBe(user._id.toString());
});
it('should return 404 for non-existent user', async () => {
const fakeId = '507f1f77bcf86cd799439011';
await request(app)
.get(`/api/users/${fakeId}`)
.expect(404);
});
});
describe('PUT /api/users/:id', () => {
it('should update user', async () => {
const user = await User.create({
email: '[email protected]',
name: 'Update User',
password: 'Password123'
});
const response = await request(app)
.put(`/api/users/${user._id}`)
.send({ name: 'Updated Name' })
.expect(200);
expect(response.body.user.name).toBe('Updated Name');
});
});
describe('DELETE /api/users/:id', () => {
it('should delete user', async () => {
const user = await User.create({
email: '[email protected]',
name: 'Delete User',
password: 'Password123'
});
await request(app)
.delete(`/api/users/${user._id}`)
.expect(204);
const deletedUser = await User.findById(user._id);
expect(deletedUser).toBeNull();
});
});
});
```
### Authentication Testing
```javascript
// tests/integration/auth.test.js
const request = require('supertest');
const app = require('../../src/app');
const User = require('../../src/models/User');
describe('Authentication', () => {
let authToken;
let testUser;
beforeEach(async () => {
// Create test user
testUser = await User.create({
email: '[email protected]',
name: 'Auth User',
password: 'Password123'
});
// Login to get token
const response = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'Password123'
});
authToken = response.body.token;
});
describe('POST /api/auth/login', () => {
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'Password123'
})
.expect(200);
expect(response.body).toHaveProperty('token');
expect(response.body).toHaveProperty('user');
});
it('should reject invalid credentials', async () => {
await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'WrongPassword'
})
.expect(401);
});
});
describe('GET /api/auth/me', () => {
it('should get current user with valid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.user.email).toBe('[email protected]');
});
it('should reject request without token', async () => {
await request(app)
.get('/api/auth/me')
.expect(401);
});
it('should reject request with invalid token', async () => {
await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
});
});
});
```
### Test Factories and Fixtures
```javascript
// tests/factories/userFactory.js
const User = require('../../src/models/User');
let userCount = 0;
exports.createUser = async (overrides = {}) => {
userCount++;
const defaultData = {
email: `user${userCount}@example.com`,
name: `User ${userCount}`,
password: 'Password123'
};
return User.create({ ...defaultData, ...overrides });
};
exports.createUsers = async (count, overrides = {}) => {
const users = [];
for (let i = 0; i < count; i++) {
users.push(await exp...
© bobmatnyc · MIT · bobmatnyc/claude-mpm-skills