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.
// 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
- 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.
// 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
- Always use HTTPS in production. Never log sensitive data like passwords or tokens. Regularly update your dependencies to patch security vulnerabilities.
