Programming 4 min read

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

Suresh S Suresh S
How to Build a REST API with Node.js and Express in 10 Minutes

The Challenge Every Developer Faces

You’re building a new project, and you need a backend API. Fast. The clock is ticking, the coffee is getting cold, and you don’t have time to set up complex frameworks or learn new paradigms. You just need something that works, something that handles HTTP requests, talks to a database, and returns JSON.

Well, you’re in luck. In the next 10 minutes, I’m going to walk you through building a production-ready REST API using Node.js and Express. No fluff, no over-engineering—just the essentials that work.

By the end of this tutorial, you’ll have a working API with:

  • ✅ CRUD operations (Create, Read, Update, Delete)
  • ✅ Environment configuration
  • ✅ Error handling
  • ✅ Route structure
  • ✅ Database integration (MongoDB)
  • ✅ Authentication ready to plug in

Let’s get started. Your API is 10 minutes away.


Step 0: Prerequisites (30 seconds)

Before we dive in, make sure you have:

node --version  # Should be v16 or higher
npm --version   # v7 or higher

If you don’t have Node.js installed, download it here. I’ll wait.

Ready? Let’s go.


Step 1: Initialize Your Project (30 seconds)

Create a new directory and initialize a Node.js project:

mkdir my-awesome-api
cd my-awesome-api
npm init -y

The -y flag accepts all defaults, saving you precious seconds.


Step 2: Install Dependencies (45 seconds)

We need three core packages:

npm install express dotenv cors helmet morgan
npm install --save-dev nodemon

Here’s what each does:

  • Express: The web framework that does the heavy lifting
  • dotenv: Loads environment variables from a .env file
  • cors: Enables Cross-Origin Resource Sharing
  • helmet: Adds security headers
  • morgan: Logs HTTP requests
  • nodemon: Auto-restarts the server on file changes (development only)

Step 3: Project Structure (30 seconds)

Create this folder structure:

mkdir src
cd src
mkdir routes controllers models middleware utils
cd ..

Your project should look like this:

my-awesome-api/
├── src/
│   ├── routes/
│   ├── controllers/
│   ├── models/
│   ├── middleware/
│   └── utils/
├── .env
├── .gitignore
├── package.json
└── server.js

Step 4: Environment Configuration (30 seconds)

Create a .env file in your root directory:

PORT=5000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/myapi
JWT_SECRET=your-super-secret-key-change-this

Security Note: Never commit your .env file! Add it to .gitignore.

Create a .gitignore file:

node_modules/
.env
.DS_Store
dist/
build/

Step 5: The Server Setup (2 minutes)

Create server.js in your root directory:

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');

const app = express();
const PORT = process.env.PORT || 5000;

// Middleware
app.use(helmet()); // Security headers
app.use(cors()); // CORS handling
app.use(morgan('dev')); // Logging
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'success',
    message: 'API is running smoothly',
    timestamp: new Date().toISOString()
  });
});

// Routes will go here
app.use('/api/v1', require('./src/routes'));

// 404 handler
app.use((req, res, next) => {
  res.status(404).json({
    status: 'error',
    message: 'Route not found'
  });
});

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

// Start server
app.listen(PORT, () => {
  console.log(`🚀 Server running on http://localhost:${PORT}`);
  console.log(`📊 Environment: ${process.env.NODE_ENV}`);
});

Step 6: Database Connection (1 minute)

Let’s set up MongoDB. First, install Mongoose:

npm install mongoose

Create src/config/database.js:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    
    console.log(`✅ MongoDB Connected: ${conn.connection.host}`);
    return conn;
  } catch (error) {
    console.error(`❌ Error connecting to MongoDB: ${error.message}`);
    process.exit(1);
  }
};

module.exports = connectDB;

Now update your server.js to include the database connection:

const connectDB = require('./src/config/database');

// Connect to database
connectDB();

// Rest of your server code...

Quick Tip: Using MongoDB Atlas? Replace your MONGODB_URI with your Atlas connection string.


Step 7: Build Your First Model (1 minute)

Create src/models/User.js:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: [true, 'Please provide a username'],
    unique: true,
    trim: true,
    minlength: [3, 'Username must be at least 3 characters']
  },
  email: {
    type: String,
    required: [true, 'Please provide an email'],
    unique: true,
    lowercase: true,
    match: [
      /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
      'Please provide a valid email'
    ]
  },
  password: {
    type: String,
    required: [true, 'Please provide a password'],
    minlength: [6, 'Password must be at least 6 characters'],
    select: false // Don't return password by default
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  isActive: {
    type: Boolean,
    default: true
  }
}, {
  timestamps: true // Adds createdAt and updatedAt
});

// Hash password before saving
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// Method to compare passwords
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

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

Step 8: Create Your Controllers (2 minutes)

Create src/controllers/userController.js:

const User = require('../models/User');

// @desc    Get all users
// @route   GET /api/v1/users
// @access  Public
exports.getUsers = async (req, res, next) => {
  try {
    const users = await User.find().select('-__v');
    
    res.status(200).json({
      status: 'success',
      count: users.length,
      data: users
    });
  } catch (error) {
    next(error);
  }
};

// @desc    Get single user
// @route   GET /api/v1/users/:id
// @access  Public
exports.getUser = async (req, res, next) => {
  try {
    const user = await User.findById(req.params.id);
    
    if (!user) {
      return res.status(404).json({
        status: 'error',
        message: 'User not found'
      });
    }
    
    res.status(200).json({
      status: 'success',
      data: user
    });
  } catch (error) {
    next(error);
  }
};

// @desc    Create user
// @route   POST /api/v1/users
// @access  Public
exports.createUser = async (req, res, next) => {
  try {
    const { username, email, password, role } = req.body;
    
    // Check if user already exists
    const existingUser = await User.findOne({ $or: [{ email }, { username }] });
    if (existingUser) {
      return res.status(400).json({
        status: 'error',
        message: 'User with this email or username already exists'
      });
    }
    
    const user = await User.create({
      username,
      email,
      password,
      role
    });
    
    // Remove password from response
    user.password = undefined;
    
    res.status(201).json({
      status: 'success',
      data: user
    });
  } catch (error) {
    next(error);
  }
};

// @desc    Update user
// @route   PUT /api/v1/users/:id
// @access  Public
exports.updateUser = async (req, res, next) => {
  try {
    const { username, email, role } = req.body;
    
    const user = await User.findByIdAndUpdate(
      req.params.id,
      { username, email, role },
      { new: true, runValidators: true }
    );
    
    if (!user) {
      return res.status(404).json({
        status: 'error',
        message: 'User not found'
      });
    }
    
    res.status(200).json({
      status: 'success',
      data: user
    });
  } catch (error) {
    next(error);
  }
};

// @desc    Delete user
// @route   DELETE /api/v1/users/:id
// @access  Public
exports.deleteUser = async (req, res, next) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    
    if (!user) {
      return res.status(404).json({
        status: 'error',
        message: 'User not found'
      });
    }
    
    res.status(200).json({
      status: 'success',
      message: 'User deleted successfully'
    });
  } catch (error) {
    next(error);
  }
};

Step 9: Set Up Routes (1 minute)

Create src/routes/index.js:

const express = require('express');
const router = express.Router();

// Import route modules
const userRoutes = require('./userRoutes');

// Mount routes
router.use('/users', userRoutes);

// Add more routes here as your API grows
// router.use('/posts', require('./postRoutes'));
// router.use('/comments', require('./commentRoutes'));

module.exports = router;

Create src/routes/userRoutes.js:

const express = require('express');
const router = express.Router();
const {
  getUsers,
  getUser,
  createUser,
  updateUser,
  deleteUser
} = require('../controllers/userController');

// Define routes
router.route('/')
  .get(getUsers)
  .post(createUser);

router.route('/:id')
  .get(getUser)
  .put(updateUser)
  .delete(deleteUser);

module.exports = router;

Step 10: Test Your API (30 seconds)

Add a start script to your package.json:

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}

Now run your development server:

npm run dev

You should see:

🚀 Server running on http://localhost:5000
📊 Environment: development
✅ MongoDB Connected: localhost

Step 11: Test Your Endpoints (1 minute)

Open Postman, Thunder Client, or just use curl:

Create a user (POST)

curl -X POST http://localhost:5000/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"username":"johndoe","email":"john@example.com","password":"password123"}'

Get all users (GET)

curl http://localhost:5000/api/v1/users

Get a specific user (GET)

curl http://localhost:5000/api/v1/users/USER_ID_HERE

Update a user (PUT)

curl -X PUT http://localhost:5000/api/v1/users/USER_ID_HERE \
  -H "Content-Type: application/json" \
  -d '{"username":"johnsmith","role":"admin"}'

Delete a user (DELETE)

curl -X DELETE http://localhost:5000/api/v1/users/USER_ID_HERE

🎉 Congratulations! Your REST API is Live!

You’ve just built a fully functional REST API in under 10 minutes. Here’s what you accomplished:

  • ✅ Express server with proper middleware
  • ✅ MongoDB integration with Mongoose
  • ✅ Full CRUD operations
  • ✅ Error handling
  • ✅ Environment configuration
  • ✅ Clean project structure
  • ✅ Production-ready setup

What’s Next? Taking It Further

Your API is ready, but there’s always room for improvement. Here are 5 enhancements to make it production-ready:

1. Add Authentication with JWT

npm install jsonwebtoken bcryptjs

2. Add Input Validation

npm install express-validator

3. Add Rate Limiting

npm install express-rate-limit

4. Add API Documentation

npm install swagger-jsdoc swagger-ui-express

5. Write Unit Tests

npm install --save-dev jest supertest

Common Issues and Solutions

Issue: “Cannot connect to MongoDB”

  • Check your connection string
  • Ensure MongoDB is running (mongod in terminal)
  • If using Atlas, whitelist your IP address

Issue: “Port 5000 already in use”

  • Kill the process: lsof -i :5000 then kill -9 PID
  • Or change the PORT in .env

Issue: “MODULE_NOT_FOUND”

  • Run npm install to install missing dependencies

Final Thoughts

Building a REST API doesn’t have to be complicated. With Node.js and Express, you can have a production-ready API in minutes. The key is knowing what tools to use and having a solid structure from the start.

This setup scales beautifully—I’ve used this exact architecture for APIs handling millions of requests. The beauty is in its simplicity: add more models, more routes, more controllers, and your API grows naturally.


Share Your Success

Did you build something cool with this tutorial? I want to hear about it! Share your project in the comments or reach out on Twitter.

Follow for more: Quick tutorials, system design insights, and the human side of software engineering.


Resources:


Built something awesome? Drop a comment below—I read every single one. 🚀

Suresh S

Written by Suresh S

Systems Engineer & Tech Educator with 10+ years of experience in Linux Administration, Cloud Computing, and Cybersecurity. Founder of FreeTechLearner, dedicated to creating practical tutorials that help students and professionals build real-world skills.

Share this post:

Discussion

Loading comments...