Sunday, March 1, 2026
HomeTechnologyBuilding a Production-Ready REST API with Node.js, Express, and MongoDB

Building a Production-Ready REST API with Node.js, Express, and MongoDB

  • Node.js + Express
  • MongoDB (with Mongoose)
  • Environment variables
  • JWT authentication
  • Proper folder structure
  • Production-ready patterns


Building a Production-Ready REST API with Node.js, Express, and MongoDB

A simple CRUD API is a good start. But real-world applications need:

  • A database
  • Authentication
  • Environment configuration
  • Clean structure
  • Proper error handling

In this guide, we’ll build a structured, scalable REST API with MongoDB and JWT authentication.


Tech Stack

  • Node.js
  • Express
  • MongoDB
  • Mongoose
  • JWT (JSON Web Tokens)
  • dotenv

Step 1: Initialize the Project

mkdir production-api
cd production-api
npm init -y

Install dependencies:

npm install express mongoose dotenv jsonwebtoken bcryptjs

Install dev dependency:

npm install --save-dev nodemon

Add this to package.json:

"scripts": {
  "dev": "nodemon server.js"
}

Step 2: Project Structure

Create this structure:

/config
/controllers
/middleware
/models
/routes
server.js
.env

This keeps logic separated and easier to maintain.


Step 3: Environment Variables

Create a .env file:

PORT=5000
MONGO_URI=mongodb://localhost:27017/production-api
JWT_SECRET=supersecretkey

Never hardcode secrets in your source code.


Step 4: Database Connection

Create config/db.js:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log("MongoDB connected");
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

module.exports = connectDB;

Step 5: Create the Server Entry Point

server.js:

require('dotenv').config();
const express = require('express');
const connectDB = require('./config/db');

const app = express();

connectDB();

app.use(express.json());

app.use('/api/users', require('./routes/userRoutes'));

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Step 6: Create User Model

models/User.js:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true,
    unique: true
  },
  password: {
    type: String,
    required: true
  }
}, { timestamps: true });

module.exports = mongoose.model('User', userSchema);

Step 7: Create Authentication Controller

controllers/userController.js:

const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

exports.registerUser = async (req, res) => {
  const { name, email, password } = req.body;

  const userExists = await User.findOne({ email });
  if (userExists) {
    return res.status(400).json({ message: "User already exists" });
  }

  const hashedPassword = await bcrypt.hash(password, 10);

  const user = await User.create({
    name,
    email,
    password: hashedPassword
  });

  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
    expiresIn: "1d"
  });

  res.status(201).json({ token });
};

Step 8: Create Routes

routes/userRoutes.js:

const express = require('express');
const router = express.Router();
const { registerUser } = require('../controllers/userController');

router.post('/register', registerUser);

module.exports = router;

Step 9: Add Authentication Middleware

middleware/authMiddleware.js:

const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
  const token = req.headers.authorization;

  if (!token) {
    return res.status(401).json({ message: "Not authorized" });
  }

  try {
    const decoded = jwt.verify(token.split(' ')[1], process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ message: "Token invalid" });
  }
};

Now you can protect routes by adding this middleware.


Step 10: Protect a Route Example

Add to userRoutes.js:

const authMiddleware = require('../middleware/authMiddleware');

router.get('/profile', authMiddleware, async (req, res) => {
  res.json({ message: "Protected route accessed", user: req.user });
});

Now only users with valid JWT tokens can access /profile.


What Makes This Production-Ready?

Compared to the beginner version:

  • Real database
  • Password hashing
  • JWT authentication
  • Environment variables
  • Modular folder structure
  • Scalable design

This is the foundation of most SaaS backends.


What to Add Next (Real-World Improvements)

To go further:

  • Input validation with Joi or Zod
  • Global error handling middleware
  • Rate limiting
  • CORS configuration
  • Helmet for security headers
  • Refresh tokens
  • Logging with Winston
  • Unit and integration testing
  • Docker setup for deployment

Final Thoughts

A real API isn’t just CRUD. It’s:

  • Secure
  • Structured
  • Maintainable
  • Scalable

If you’re building portfolio projects, startups, or backend systems, this is the level you should aim for.


Next direction options:

  1. Convert this to TypeScript
  2. Add MongoDB relationships and advanced queries
  3. Build a role-based access control (RBAC) system
  4. Turn this into a Dockerized deployment guide
  5. Add Swagger API documentation

RELATED ARTICLES

Most Popular

Recent Comments