Building a RESTful API with Node.js, Express, and MongoDB

Introduction

In today’s web development landscape, building RESTful APIs has become a crucial skill for developers. Whether you’re creating a simple application or a complex system, REST APIs provide a standardized way for different software components to communicate with each other over the web. In this tutorial, we’ll walk through the process of building a RESTful API using Node.js, Express, and MongoDB, focusing on CRUD operations (Create, Read, Update, Delete) for managing products.

For more information on REST API, check this blog : Designing APIs Using REST Specifications: A Comprehensive Guide – Learn Code Camp

Technologies Used

  • Node.js: A JavaScript runtime built on Chrome’s V8 JavaScript engine.
  • Express: A minimalist web framework for Node.js, which simplifies the process of building web applications and APIs.
  • MongoDB: A NoSQL database that stores data in flexible, JSON-like documents.

Setting Up the Project

First, let’s set up our project structure and install the necessary dependencies. Create a new directory for your project and navigate into it.

mkdir node-express-mongodb-api
cd node-express-mongodb-api

Initialize a new Node.js project and install Express, Mongoose (for MongoDB integration), and any other dependencies needed.

npm install express mongodb mongoose
npm install --save-dev nodemon

Creating the MongoDB Database

To install the mongo db atlas locally run these commands

brew install mongodb-atlas 
atlas deployments setup

To see the collections in mongo db from mongosh, run these commands

1. show collections; // Display all collections
 2. show tables     // Display all collections
 3. db.getCollectionNames();   // Return array of collection. Example :[ "orders", "system.profile" ]

Some other useful commands

show dbs; // to show all the db present
db.products.find() // to list 20 documents from collection products.

Setting Up the Server

Now, let’s create the main file index.js where we’ll set up our Express server and define the routes for our API.

// index.js
const express = require("express");
const mongoose = require("mongoose");
const productRoute = require("./routes/product.route.js");

const app = express();

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// Routes
app.use("/api/products", productRoute);

// Home route
app.get("/", (req, res) => {
  res.send("Hello from Node API Server");
});

// Connect to MongoDB and start the server
mongoose
  .connect("mongodb://localhost:63233/test?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.1.5", {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log("Connected to MongoDB");
    app.listen(3000, () => {
      console.log("Server is running on port 3000");
    });
  })
  .catch((error) => {
    console.error("Connection to MongoDB failed:", error);
  });

In the above code:

  • We import Express and Mongoose, and define our Express app.
  • Middleware functions are added to parse incoming requests with JSON payloads and URL-encoded bodies.
  • Routes are defined using the /api/products prefix, which delegates further handling to productRoute.
  • A simple home route is set up to verify that the server is running.
  • We connect to the MongoDB database using Mongoose and start the Express server on port 3000.

Defining the Product Model

Next, let’s define the product model that represents the structure of our data in MongoDB.

// product.model.js
const mongoose = require("mongoose");

const ProductSchema = mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, "Please enter product name"],
    },
    quantity: {
      type: Number,
      required: true,
      default: 0,
    },
    price: {
      type: Number,
      required: true,
      default: 0,
    },
    image: {
      type: String,
      required: false,
    },
  },
  {
    timestamps: true,
  }
);

const Product = mongoose.model("Product", ProductSchema);

module.exports = Product;

In the ProductSchema:

  • We define the fields for our product model (name, quantity, price, image).
  • Field validation rules are specified using Mongoose schema types and options.
  • We enable timestamps to automatically add createdAt and updatedAt fields to each document.

Creating CRUD Operations

Now, let’s create the controller functions for handling CRUD operations on products.

// product.controller.js
const Product = require("../models/product.model");

const getProducts = async (req, res) => {
  try {
    const products = await Product.find({});
    res.status(200).json(products);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const getProduct = async (req, res) => {
  try {
    const { id } = req.params;
    const product = await Product.findById(id);
    if (!product) {
      return res.status(404).json({ message: "Product not found" });
    }
    res.status(200).json(product);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const createProduct = async (req, res) => {
  try {
    const product = await Product.create(req.body);
    res.status(201).json(product);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const updateProduct = async (req, res) => {
  try {
    const { id } = req.params;
    const product = await Product.findByIdAndUpdate(id, req.body, {
      new: true,
    });
    if (!product) {
      return res.status(404).json({ message: "Product not found" });
    }
    res.status(200).json(product);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

const deleteProduct = async (req, res) => {
  try {
    const { id } = req.params;
    const product = await Product.findByIdAndDelete(id);
    if (!product) {
      return res.status(404).json({ message: "Product not found" });
    }
    res.status(200).json({ message: "Product deleted successfully" });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

module.exports = {
  getProducts,
  getProduct,
  createProduct,
  updateProduct,
  deleteProduct,
};

In the product.controller.js:

  • Controller functions are defined for handling various CRUD operations on products.
  • These functions use asynchronous syntax with async/await for working with MongoDB queries.
  • Error handling is implemented to catch any potential errors and return appropriate HTTP status codes and error messages.

Setting Up Product Routes

Finally, let’s define the routes for our products API in product.route.js.

// product.route.js
const express = require("express");
const router = express.Router();
const {
  getProducts,
  getProduct,
  createProduct,
  updateProduct,
  deleteProduct,
} = require("../controllers/product.controller");

router.get("/", getProducts);
router.get("/:id", getProduct);
router.post("/", createProduct);
router.put("/:id", updateProduct);
router.delete("/:id", deleteProduct);

module.exports = router;

Here, we define routes for fetching all products, fetching a single product by ID, creating a new product, updating an existing product, and deleting a product.

Conclusion

In this tutorial, we’ve learned how to build a RESTful API with Node.js, Express, and MongoDB. We’ve covered setting up the server, defining the database model, implementing CRUD operations, and setting up routes to handle API requests. With this foundation, you can extend the API further by adding more features, implementing authentication and authorization, and optimizing performance for production use. Happy coding!

References: haris-bit/simple-crud-app-backend (github.com)

Leave a comment