This comprehensive guide is designed for developers looking to build a production-ready REST API using Node.js, Express, and MongoDB. By the end of this guide, you will have a fully functional REST API that includes features such as JWT authentication, role-based access control, input validation, error handling, and logging. The guide assumes you have basic knowledge of JavaScript, Node.js, and MongoDB. You will need to have Node.js and MongoDB installed on your machine to follow along.
Prerequisites & System Requirements
Ensure your development workstation or staging server fulfills the following prerequisites before initiating commands:
Target System Architecture Diagram
Interactive Execution Checklist
Step-by-Step Implementation Guide
Step 1: Set Up the Project Structure
Create a new project folder and initialize a new Node.js project using npm init. Create the following folders: controllers, models, routes, services, and utils. This structure will help keep your code organized and maintainable.
Execute Command Terminal:
mkdir my-api && cd my-api && npm init -yStep 2: Install Required Dependencies
Install the required dependencies for the project, including Express, MongoDB, and JWT. Use npm install to install the dependencies.
Execute Command Terminal:
npm install express mongoose jsonwebtoken bcryptjsStep 3: Configure Express
Create a new file called app.js and require the installed dependencies. Create an instance of the Express app and configure it to use JSON parsing and URL encoding.
Execute Command Terminal:
const express = require('express'); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true }));Step 4: Connect to MongoDB
Create a new file called db.js and require the mongoose dependency. Connect to the MongoDB database using the mongoose.connect method.
Execute Command Terminal:
const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/my-api', { useNewUrlParser: true, useUnifiedTopology: true });Step 5: Implement JWT Authentication
Create a new file called auth.js and require the jsonwebtoken dependency. Implement a function to generate a JWT token and another function to verify the token.
Execute Command Terminal:
const jwt = require('jsonwebtoken'); const generateToken = (user) => { return jwt.sign(user, process.env.SECRET_KEY, { expiresIn: '1h' }); }; const verifyToken = (req, res, next) => { const token = req.header('Authorization'); if (!token) return res.status(401).send('Access denied'); try { const decoded = jwt.verify(token, process.env.SECRET_KEY); req.user = decoded; next(); } catch (ex) { return res.status(400).send('Invalid token'); } };Step 6: Implement Role-Based Access Control
Create a new file called roles.js and define the roles for the API. Implement a function to check if a user has a certain role.
Execute Command Terminal:
const roles = { admin: ['create', 'read', 'update', 'delete'], user: ['read'] }; const hasRole = (user, role) => { return user.roles.includes(role); };Step 7: Implement Input Validation
Create a new file called validation.js and require the joi dependency. Implement a function to validate user input using Joi.
Execute Command Terminal:
const Joi = require('joi'); const validateUser = (user) => { const schema = Joi.object({ name: Joi.string().required(), email: Joi.string().email().required() }); return schema.validate(user); };Step 8: Implement Error Handling
Create a new file called errors.js and define a custom error class. Implement a function to handle errors and return a response to the client.
Execute Command Terminal:
class CustomError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; } }; const handleError = (err, res) => { const error = new CustomError(err.message, err.statusCode); res.status(error.statusCode).send({ message: error.message }); };Step 9: Implement Logging
Create a new file called logger.js and require the winston dependency. Implement a function to log messages using Winston.
Execute Command Terminal:
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }) ] }); const logMessage = (message) => { logger.info(message); };Step 10: Implement Swagger Documentation
Create a new file called swagger.js and require the swagger-ui-express dependency. Implement a function to serve the Swagger UI.
Execute Command Terminal:
const swaggerUi = require('swagger-ui-express'); const swaggerDocument = require('./swagger.json'); const serveSwagger = (app) => { app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); };Step 11: Implement Helmet
Create a new file called helmet.js and require the helmet dependency. Implement a function to configure Helmet.
Execute Command Terminal:
const helmet = require('helmet'); const configureHelmet = (app) => { app.use(helmet()); };Step 12: Implement Rate Limiting
Create a new file called rate-limiting.js and require the express-rate-limit dependency. Implement a function to configure rate limiting.
Execute Command Terminal:
const rateLimit = require('express-rate-limit'); const configureRateLimiting = (app) => { const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use(limiter); };Step 13: Implement CORS
Create a new file called cors.js and require the cors dependency. Implement a function to configure CORS.
Execute Command Terminal:
const cors = require('cors'); const configureCORS = (app) => { app.use(cors()); };Step 14: Configure Environment Variables
Create a new file called env.js and require the dotenv dependency. Implement a function to configure environment variables.
Execute Command Terminal:
const dotenv = require('dotenv'); const configureEnvironmentVariables = () => { dotenv.config(); };Step 15: Test the API
Use a tool like Postman to test the API endpoints. Make sure to test all the endpoints and verify that they are working as expected.
Step 16: Deploy the API
Deploy the API to a cloud platform like AWS or Google Cloud. Make sure to configure the environment variables and deploy the API to a production environment.
Step 17: Monitor the API
Use a tool like New Relic to monitor the API performance. Make sure to monitor the API performance and fix any issues that arise.
Pro Tips & Optimizations
Common Pitfalls to Avoid
In this guide, we have learned how to build a production-ready REST API using Node.js, Express, and MongoDB. We have implemented features like JWT authentication, role-based access control, input validation, error handling, and logging. We have also deployed the API to a production environment and monitored its performance. By following this guide, you can build a scalable and secure REST API that meets your needs.
Production Best Practices & Hardening
Security Hardening
Disable root SSH access, enforce key-based auth, and enable UFW firewall on ports 80/443.
Memory Management
Set Node.js max-old-space-size to 80% of total RAM to avoid Linux OOM-killer crashes.
Frequently Asked Questions
Yes, all NGINX, Docker, and PM2 deployment steps can be packaged into Infrastructure as Code (IaC) playbooks.
Need Help Implementing This?
Partner with Inteliny's principal architects to audit your stack, automate CI/CD, and accelerate deployment.