Lesson 19 of 20

Node.js Best Practices

Project Structure and Security

A well-organized project structure makes your code maintainable and scalable. Separate your application by feature or layer — routes, controllers, models, middleware, and utilities.

Security is critical for production Node.js applications. Use established packages to protect against common vulnerabilities like XSS, CSRF, and brute force attacks.

Example
// Recommended project structure
my-api/
├── src/
│   ├── config/        # Configuration files
│   │   └── index.js
│   ├── controllers/   # Request handlers
│   │   └── userController.js
│   ├── middleware/     # Custom middleware
│   │   ├── auth.js
│   │   └── errorHandler.js
│   ├── models/        # Database models
│   │   └── User.js
│   ├── routes/        # Route definitions
│   │   ├── index.js
│   │   └── userRoutes.js
│   ├── utils/         # Helper functions
│   │   └── AppError.js
│   └── app.js         # Express app setup
├── tests/             # Test files
├── .env               # Environment variables
├── .env.example       # Template for .env
├── .gitignore
├── package.json
└── server.js          # Entry point
  • Separate concerns — Routes, controllers, models, middleware in their own folders
  • Single responsibility — Each file does one thing well
  • Config module — Centralize configuration with validation
  • Error handling — Global error handler for consistent error responses
  • Environment-based config — Different settings for dev, staging, production
Notes
  • Start simple and add structure as your project grows. Do not over-engineer a small application.

Security, Logging, and Testing

Production Node.js applications need security hardening, structured logging, and automated tests. These are not optional extras — they are essential for any application that handles user data.

Use helmet for HTTP security headers, cors for cross-origin control, rate limiting to prevent abuse, and a testing framework like Jest to verify your code works correctly.

Example
// npm install helmet cors express-rate-limit winston jest

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const app = express();

// Security headers
app.use(helmet());

// CORS
app.use(cors({ origin: process.env.CORS_ORIGIN }));

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Max 100 requests per window
  message: { error: 'Too many requests, try again later' }
});
app.use('/api', limiter);

// Body size limit
app.use(express.json({ limit: '10kb' }));

// Simple test example with Jest
// tests/math.test.js
const { add } = require('../src/utils/math');

describe('Math utilities', () => {
  test('adds two numbers correctly', () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
  });

  test('handles zero', () => {
    expect(add(0, 0)).toBe(0);
  });
});

// Run tests: npm test
  • helmet — Sets security HTTP headers automatically
  • cors — Controls which domains can access your API
  • express-rate-limit — Prevents brute force and DDoS attacks
  • express.json({ limit: '10kb' }) — Prevent large payload attacks
  • Jest — Testing framework for unit and integration tests
  • winston — Production-ready logging with levels and transports
Notes
  • Always use HTTPS in production. Never log sensitive data like passwords or tokens. Regularly update your dependencies to patch security vulnerabilities.