Based on Planner and Memory Manager analysis: New Skills (8): - nodejs-express-patterns: App structure, routing, middleware - nodejs-security-owasp: OWASP Top 10 security practices - nodejs-testing-jest: Unit/integration tests, mocking - nodejs-auth-jwt: JWT authentication, OAuth, sessions - nodejs-error-handling: Error classes, middleware, async handlers - nodejs-middleware-patterns: Auth, validation, rate limiting - nodejs-db-patterns: SQLite, PostgreSQL, MongoDB patterns - nodejs-npm-management: package.json, scripts, dependencies New Rules: - nodejs.md: Code style, security, best practices Updated: - backend-developer.md: Added skills reference table Milestone: #48 NodeJS Development Coverage Related: Planner & Memory Manager analysis results
8.2 KiB
8.2 KiB
NodeJS Express Patterns
Comprehensive patterns for building production-ready Express.js applications.
Overview
This skill provides canonical patterns for Express.js development including routing, middleware, error handling, and best practices.
Express Application Structure
backend/
├── src/
│ ├── app.js # Express app setup
│ ├── server.js # Server entry point
│ ├── config/
│ │ ├── index.js # Config aggregation
│ │ ├── database.js # DB config
│ │ └── security.js # Security config
│ ├── routes/
│ │ ├── index.js # Route aggregator
│ │ ├── api/ # Public API routes
│ │ │ ├── users.js
│ │ │ ├── posts.js
│ │ │ └── index.js
│ │ └── admin/ # Admin API routes
│ │ ├── auth.js
│ │ ├── users.js
│ │ └── index.js
│ ├── controllers/ # Route handlers
│ ├── services/ # Business logic
│ ├── models/ # Data models
│ ├── middleware/
│ │ ├── auth.js
│ │ ├── validation.js
│ │ ├── error.js
│ │ └── rateLimit.js
│ ├── utils/
│ └── db/
│ ├── connection.js
│ └── migrations/
├── tests/
├── package.json
└── docker-compose.yml
Core Patterns
1. App Bootstrap (app.js)
// backend/src/app.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const { errorHandler, notFoundHandler } = require('./middleware/error');
const routes = require('./routes');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN?.split(',') || '*',
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', limiter);
// Body parsing
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
// Static files
app.use('/media', express.static(path.join(__dirname, '../uploads')));
// Routes
app.use('/api', routes.api);
app.use('/api/admin', routes.admin);
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Error handlers (must be last)
app.use(notFoundHandler);
app.use(errorHandler);
module.exports = app;
2. Server Entry Point (server.js)
// backend/src/server.js
const app = require('./app');
const { connectDB } = require('./db/connection');
const PORT = process.env.PORT || 3000;
const HOST = process.env.HOST || '0.0.0.0';
async function startServer() {
try {
// Connect to database
await connectDB();
console.log('✓ Database connected');
// Start server
const server = app.listen(PORT, HOST, () => {
console.log(`✓ Server running on http://${HOST}:${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => gracefulShutdown(server));
process.on('SIGINT', () => gracefulShutdown(server));
return server;
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
function gracefulShutdown(server) {
console.log('Received shutdown signal. Closing connections...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
// Force close after 10s
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 10000);
}
startServer();
3. Routing Patterns
// backend/src/routes/api/index.js
const express = require('express');
const router = express.Router();
const users = require('./users');
const posts = require('./posts');
const { validateRequest } = require('../../middleware/validation');
// Resource routes
router.use('/users', users);
router.use('/posts', posts);
// Validation middleware
router.use(validateRequest);
module.exports = router;
// backend/src/routes/api/users.js
const express = require('express');
const router = express.Router();
const userController = require('../../controllers/userController');
const { authenticate, authorize } = require('../../middleware/auth');
const { validateUser } = require('../../middleware/validation');
// Public routes
router.post('/register', validateUser, userController.register);
router.post('/login', userController.login);
// Protected routes
router.use(authenticate);
router.get('/me', userController.getProfile);
router.put('/me', validateUser, userController.updateProfile);
// Admin routes
router.get('/', authorize('admin'), userController.listUsers);
module.exports = router;
4. Controller Pattern
// backend/src/controllers/userController.js
const userService = require('../services/userService');
const { AppError } = require('../middleware/error');
class UserController {
async register(req, res, next) {
try {
const { email, password, name } = req.body;
const user = await userService.create({ email, password, name });
res.status(201).json({
status: 'success',
data: { user: user.toSafeObject() }
});
} catch (error) {
next(error);
}
}
async getProfile(req, res, next) {
try {
const user = await userService.findById(req.user.id);
if (!user) {
throw new AppError('User not found', 404);
}
res.json({
status: 'success',
data: { user: user.toSafeObject() }
});
} catch (error) {
next(error);
}
}
}
module.exports = new UserController();
5. Service Layer Pattern
// backend/src/services/userService.js
const User = require('../models/User');
const { hashPassword, comparePassword } = require('../utils/password');
const { generateToken } = require('../utils/jwt');
class UserService {
async create(userData) {
// Check if user exists
const existingUser = await User.findByEmail(userData.email);
if (existingUser) {
throw new AppError('Email already registered', 409);
}
// Hash password
const hashedPassword = await hashPassword(userData.password);
// Create user
const user = await User.create({
...userData,
password: hashedPassword
});
return user;
}
async authenticate(email, password) {
const user = await User.findByEmail(email);
if (!user) {
throw new AppError('Invalid credentials', 401);
}
const isValid = await comparePassword(password, user.password);
if (!isValid) {
throw new AppError('Invalid credentials', 401);
}
const token = generateToken({ id: user.id, role: user.role });
return { user, token };
}
}
module.exports = new UserService();
6. Async Handler Wrapper
// backend/src/middleware/asyncHandler.js
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;
// Usage
router.get('/users/:id', asyncHandler(userController.getById));
7. Request Validation
// backend/src/middleware/validation.js
const { validationResult, body, param, query } = require('express-validator');
const validate = (validations) => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
res.status(400).json({
status: 'fail',
errors: errors.array().map(err => ({
field: err.path,
message: err.msg
}))
});
};
};
// Validation schemas
const userValidation = {
register: [
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Valid email required'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters'),
body(