Skip to main content
skill CX Codex

express-production — Claude Code skill: what it does, how to install

Provides production-grade patterns for Express.js web applications and REST APIs. Helps developers set up security headers, request middleware pipelines, and PM2 deployment.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a claude-code
Open on GitHub

What express-production does

This skill guides the AI assistant in structuring Express.js applications according to production requirements. It splits setup across src/app.js as an application factory and src/server.js as the runtime entry point. Code layouts cleanly isolate routing tables from controller logic.

Middleware execution follows a strict security-first flow. It mounts helmet headers first, resolves CORS rules, and triggers express-rate-limit before parsing incoming JSON payloads. Compression and request logging with morgan or winston run next. For failure handling, it supplies custom AppError classes alongside async wrapper routines.

Production execution relies on PM2 clustering defined via ecosystem.config.js. Servers listen for SIGTERM signals to finish inflight requests prior to exit. Missing core packages like helmet or express-rate-limit will break the startup sequence immediately.

What it helps with

  • enforce the correct middleware execution order from security headers to routes
  • handle server termination safely via SIGTERM events
  • centralize asynchronous error reporting with custom AppError utilities
  • configure PM2 process clustering for zero-downtime releases

Example prompts for the agent

  • Generate a production Express folder structure with separate app.js and server.js files.
  • Configure the Express middleware pipeline with helmet, CORS, and express-rate-limit.
  • Implement graceful shutdown logic for an Express server handling SIGTERM.

How to install express-production

1Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a claude-code

2Manually: put the files in place

~/.claude/skills/express-production/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/express-production/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about express-production

How to install express-production in Claude Code?

Run npx skills add bobmatnyc/claude-mpm-skills --skill express-production -a claude-code. The command configures the skill inside your Claude Code environment.

What does express-production do?

It equips the AI coding agent with production guidelines for Express.js. The skill enforces directory layouts, middleware sequences, error handling classes, and PM2 scripts.

Which npm packages does this Express setup expect?

The setup requires helmet, cors, express-rate-limit, and dotenv. For logging and process management, it relies on morgan, winston, and PM2.

Source text

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, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
};
```

### 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

Similar skills

All skills for Backend and APIs →

From the same set125

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor