Files
APAW/.kilo/skills/nodejs-express-patterns/SKILL.md
¨NW¨ 8fcd8f8a9b feat: add comprehensive NodeJS development skills and rules
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
2026-04-05 02:39:06 +01:00

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(