Building a REST API with Node.js and Express from Scratch

Victor UzoagbaVictor Uzoagba
5 min read

Introduction

In modern web development, REST APIs serve as the backbone of communication between the frontend and backend, enabling applications to exchange data efficiently. In this tutorial, we will build a REST API using Node.js and Express.js, one of the most popular backend frameworks for JavaScript. This API will perform CRUD (Create, Read, Update, Delete) operations on a user resource, storing data in either MongoDB or PostgreSQL.

By the end of this tutorial, you will have a fully functional API with user authentication using JSON Web Tokens (JWT), and you will also learn how to test and deploy your API.

Setting Up the Development Environment

To get started, ensure you have Node.js installed. If not, download and install it from the official Node.js website.

Once installed, verify the installation:

node -v
npm -v

Creating the Project Structure

  1. Create a new project directory and navigate into it:

     mkdir rest-api-node
     cd rest-api-node
    
  2. Initialize a new Node.js project:

     npm init -y
    

    This command generates a package.json file, which stores project metadata and dependencies.

Installing Dependencies

We need several packages to build our API:

npm install express dotenv cors morgan
  • express: The web framework for building our API

  • dotenv: Loads environment variables from a .env file

  • cors: Enables Cross-Origin Resource Sharing

  • morgan: Logs API requests for debugging

Creating a Basic Express Server

Create an index.js file and add the following code:

const express = require("express");
require("dotenv").config();

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

app.get("/", (req, res) => {
  res.send("Welcome to the REST API!");
});

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

Run the server using:

node index.js

Open http://localhost:5000/ in a browser, and you should see "Welcome to the REST API!".

Defining the API Structure and Endpoints

A REST API follows standard HTTP methods to interact with resources. Our API will manage users with the following endpoints:

MethodEndpointDescription
GET/api/usersGet all users
GET/api/users/:idGet a single user
POST/api/usersCreate a new user
PUT/api/users/:idUpdate a user
DELETE/api/users/:idDelete a user

Connecting to a Database (MongoDB/PostgreSQL)

We will implement two database options:

Option 1: Using MongoDB with Mongoose

Install MongoDB and the Mongoose ORM:

npm install mongoose

Create a database connection file (config/db.js):

const mongoose = require("mongoose");

mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const db = mongoose.connection;
db.once("open", () => console.log("Connected to MongoDB"));

Define a User model (models/User.js):

const mongoose = require("mongoose");

const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  password: String,
});

module.exports = mongoose.model("User", UserSchema);

Option 2: Using PostgreSQL with Sequelize

Install Sequelize and PostgreSQL dependencies:

npm install sequelize pg pg-hstore

Set up the database connection (config/db.js):

const { Sequelize } = require("sequelize");

const sequelize = new Sequelize(process.env.PG_URI);

sequelize.authenticate()
  .then(() => console.log("Connected to PostgreSQL"))
  .catch(err => console.log("Error:", err));

module.exports = sequelize;

Define a User model (models/User.js):

const { DataTypes } = require("sequelize");
const sequelize = require("../config/db");

const User = sequelize.define("User", {
  name: DataTypes.STRING,
  email: DataTypes.STRING,
  password: DataTypes.STRING,
});

module.exports = User;

Implementing CRUD Operations in Express

Create a route file (routes/userRoutes.js):

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

router.get("/", getUsers);
router.get("/:id", getUserById);
router.post("/", createUser);
router.put("/:id", updateUser);
router.delete("/:id", deleteUser);

module.exports = router;

Create a controller file (controllers/userController.js):

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

exports.getUsers = async (req, res) => {
  const users = await User.find();
  res.json(users);
};

exports.getUserById = async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user);
};

exports.createUser = async (req, res) => {
  const newUser = new User(req.body);
  await newUser.save();
  res.status(201).json(newUser);
};

exports.updateUser = async (req, res) => {
  const updatedUser = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
  res.json(updatedUser);
};

exports.deleteUser = async (req, res) => {
  await User.findByIdAndDelete(req.params.id);
  res.json({ message: "User deleted successfully" });
};

Implementing Authentication with JWT

Install dependencies:

npm install jsonwebtoken bcrypt

Update the controller to include user authentication (controllers/authController.js):

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

exports.register = async (req, res) => {
  const hashedPassword = await bcrypt.hash(req.body.password, 10);
  const user = new User({ ...req.body, password: hashedPassword });
  await user.save();
  res.status(201).json({ message: "User registered successfully" });
};

exports.login = async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  if (!user || !(await bcrypt.compare(req.body.password, user.password))) {
    return res.status(401).json({ message: "Invalid credentials" });
  }
  const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "1h" });
  res.json({ token });
};

Testing the API with Postman

  • Send a POST request to /api/users with user data

  • Use a GET request to retrieve all users

  • Test authentication by making requests with the JWT token

Deploying the API

  • Host the API on Render, Vercel, or Railway

  • Deploy to a VPS using PM2 and Nginx

  • Store environment variables securely

Conclusion

In this tutorial, we built a REST API with Node.js, Express, and MongoDB/PostgreSQL, covering CRUD operations, authentication, and deployment.

To extend this API, consider adding role-based authentication, request validation, and API rate limiting.

0
Subscribe to my newsletter

Read articles from Victor Uzoagba directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Victor Uzoagba
Victor Uzoagba

I'm a seasoned technical writer specializing in Python programming. With a keen understanding of both the technical and creative aspects of technology, I write compelling and informative content that bridges the gap between complex programming concepts and readers of all levels. Passionate about coding and communication, I deliver insightful articles, tutorials, and documentation that empower developers to harness the full potential of technology.