Tutorials

How to Build a REST API with Node.js and Express in 2026

Step-by-step guide to building production-ready REST APIs using Node.js, Express, and modern best practices. Includes authentication, validation, error handling, and deployment.

GoTech Studio Team GoTech Studio Team
June 12, 2026 12 min read 14 views
How to Build a REST API with Node.js and Express in 2026

REST APIs remain the backbone of modern web and mobile applications. This tutorial walks you through building a production-ready API from scratch using Node.js and Express, incorporating 2026 best practices.

 
 
 
Prerequisites
  • Node.js 20+ installed
  • Basic JavaScript knowledge
  • A code editor (VS Code recommended)
  • Postman or similar for testing
Step 1: Project Setup Create a new directory and initialize your project:
bash
 
mkdir node-api-tutorial
cd node-api-tutorial
npm init -y
 
Install dependencies:
bash
 
npm install express mongoose dotenv bcryptjs jsonwebtoken express-validator cors helmet morgan
npm install --save-dev nodemon jest supertest
 
Step 2: Basic Server Structure Create server.js:
JavaScript
 
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
require('dotenv').config();

const app = express();

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());

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

// Error handling
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
 
Step 3: Database Connection Create config/db.js:
JavaScript
 
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI);
    console.log('MongoDB Connected');
  } catch (error) {
    console.error('Database connection failed:', error);
    process.exit(1);
  }
};

module.exports = connectDB;
 
Step 4: User Model Create models/User.js:
JavaScript
 
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  name: { type: String, required: true, trim: true },
  email: { type: String, required: true, unique: true, lowercase: true },
  password: { type: String, required: true, minlength: 6 },
  createdAt: { type: Date, default: Date.now }
});

userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 12);
  next();
});

module.exports = mongoose.model('User', userSchema);
 
Step 5: Authentication Middleware Create middleware/auth.js:
JavaScript
 
const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'No token, authorization denied' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded.userId;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Token is invalid' });
  }
};
 
Step 6: Validation Create middleware/validation.js:
JavaScript
 
const { body, validationResult } = require('express-validator');

const userValidation = [
  body('name').trim().isLength({ min: 2 }).withMessage('Name must be at least 2 characters'),
  body('email').isEmail().normalizeEmail().withMessage('Valid email required'),
  body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'),
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    next();
  }
];

module.exports = { userValidation };
 
Step 7: User Routes Create routes/users.js:
JavaScript
 
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const auth = require('../middleware/auth');
const { userValidation } = require('../middleware/validation');

// Register
router.post('/register', userValidation, async (req, res) => {
  try {
    const { name, email, password } = req.body;
    
    let user = await User.findOne({ email });
    if (user) return res.status(400).json({ error: 'User already exists' });

    user = new User({ name, email, password });
    await user.save();

    const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '24h' });
    res.status(201).json({ token, user: { id: user._id, name, email } });
  } catch (error) {
    res.status(500).json({ error: 'Server error' });
  }
});

// Login
router.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(400).json({ error: 'Invalid credentials' });
    }

    const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '24h' });
    res.json({ token, user: { id: user._id, name: user.name, email } });
  } catch (error) {
    res.status(500).json({ error: 'Server error' });
  }
});

// Get Profile
router.get('/me', auth, async (req, res) => {
  try {
    const user = await User.findById(req.user).select('-password');
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: 'Server error' });
  }
});

module.exports = router;
 
Step 8: Environment Variables Create .env:
plain
 
PORT=3000
MONGODB_URI=mongodb://localhost:27017/node-api
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
 
Step 9: Testing Create tests/users.test.js:
JavaScript
 
const request = require('supertest');
const app = require('../server');

describe('User API', () => {
  it('should register a new user', async () => {
    const res = await request(app)
      .post('/api/users/register')
      .send({
        name: 'Test User',
        email: 'test@example.com',
        password: 'password123'
      });
    expect(res.statusCode).toBe(201);
    expect(res.body).toHaveProperty('token');
  });
});
 
Step 10: Deployment For production:
  • Use pm2 for process management
  • Enable HTTPS with Let's Encrypt
  • Set up environment variables securely
  • Use a reverse proxy (Nginx)
  • Implement rate limiting with express-rate-limit
Conclusion You've built a secure, validated REST API with authentication. This foundation scales to production applications. Add database indexing, caching with Redis, and API documentation with Swagger for complete production readiness.
Tags
Node.js Express REST API tutorial backend development