CRUD with ExpressJS and MongoDB (Typescript)

CRUD with ExpressJS and MongoDB (Typescript)

Introduction
As a backend developer looking to build APIs, it's essential to understand how to store and retrieve data from the database your API interacts with. While this might initially seem overwhelming, this article will guide you through creating a CRUD operation API step by step. Before diving into the practical aspects, let’s clarify some key concepts.

What is an API?

API stands for Application Programming Interface. It acts as a bridge that allows an application to communicate with a service. For example, when a mobile app needs to fetch data from a service, the service may not be written in the same language as the app. This is where an API comes in—it serves as the interface through which the app can send requests and receive responses in a way the service understands. In this context, the API facilitates interaction between the app (the client) and the service (the provider).

What is the Backend?

The backend consists of components like the API, database, load balancers, process managers, and other tools that support or enhance the service's functionality.

What are RESTful APIs?

RESTful APIs adhere to the principles of Representational State Transfer (REST), which ensure stateless communication between the client and the server.

To explain stateless interactions: Imagine entering your workplace, which requires an ID card for access. Even if the gatekeeper recognizes you, they won’t let you in without your ID. Similarly, if you leave and return, you must present your ID again, even if it’s just been a minute. This behavior mirrors how RESTful APIs operate—they don’t retain any memory of previous interactions. Each request is treated independently, and once the server sends a response, the connection is closed.

Key features of RESTful APIs include:

  • HTTP methods (e.g., GET, POST, PUT, DELETE)

  • Request headers and payloads

  • Endpoints

  • Response headers and data

  • Status codes

What is a Database?

A database is a system designed to store information permanently unless explicitly deleted. Since APIs are not meant to store data permanently, databases provide the storage solution for the information processed by the API.

MongoDB

MongoDB, derived from the word "humongous," highlights its capacity to handle vast amounts of data. It is a schemeless database, meaning you have flexibility in how you structure your data.

Mongoose

Mongoose is an ODM (Object Document Mapper) that provides a way to define and enforce schemas at the application level while interacting with MongoDB. MongoDB organizes data into documents and collections, and Mongoose enables developers to query the database and maintain structured schemas.

CRUD Operations

CRUD represents the core operations in backend development: Create, Read, Update, and Delete.

ExpressJS

ExpressJS (or Express) is a lightweight framework that simplifies the process of building APIs, making development smoother and more efficient.

Now that we’ve covered the basics, let’s begin setting up the API!

Let's begin setting up.

Step 1: Initialize a project/package

You initialize a project or package by running the command:

npm init

This will create a package.json file that will be used by pnpm to manage your project's dependencies.

Step 2: Install the Required Dependencies

To get started, you'll need to install some key dependencies: Morgan, ExpressJS, Mongoose, Cors, and Nodemon. While we've already covered ExpressJS and Mongoose, let me briefly explain the others:

  • Morgan: A logging library used to generate logs for your application. These logs can be helpful for debugging or monitoring server activity by printing details to the console while the server is running.

  • Cors: Stands for Cross-Origin Resource Sharing. This package is essential for controlling which programs or clients can interact with your API. It helps configure restrictions like allowed request methods, headers, and origins. This is particularly important when your client and API are running on separate platforms or ports.

  • Nodemon: A development tool that automatically restarts your server whenever you make changes to your project files. Think of it as a "node monitor" for easier development workflows. (Fun fact: Don’t use Nodemon in production—unless you're debugging live, which isn’t a great idea, lol!)

  • TypeScript: Adds static typing and interfaces to your JavaScript code, making it easier to maintain and debug.

You can install all these dependencies by running the following command:

npm add express mongoose cors morgan typescript && pnpm add -D nodemon

After that initialize typescript on the project by running the code:

npm tsc --init

Step 3: Create Essential Project Files

In the root directory of your project (where the package.json file is located), create the following files: server.ts, routes.ts, and model.ts. Here's what each file will be used for:

  • index.ts: This will act as the main entry point for your application, meaning it's the first file executed when the server starts.

  • crud.ts: This file will define all the API endpoints and their corresponding controllers, handling user requests and responses.

  • model.ts: This file will store the database schema, ensuring structured and consistent data for the records you’ll be managing.

  • Step 4: Paste the following into your index.js file

  •   import express, {Application} from "express";
      import mongoose from "mongoose";
      import logger from "morgan";
      import cors from "cors";
      import crudRoutes from "./crud";
    
      const app: Application = express();
    
      mongoose.connect("mongodb://localhost:27017/crud").then(() => {
          console.log("Connected to database");
      }).catch((error) => {
          console.log("Error:", error);
      });
    
      app.use(logger("dev"));
      app.use(cors({ origin: "*" }));
      app.use(express.json());
      app.use(express.urlencoded({ extended: false }));
    
      app.use("/crud", crudRoutes);
    
      app.use(function (req, res, next) {
          res.status(404).send({
              message: "Route not found"
          });
        });
    
      app.listen(4000, () => {
          console.log("The server is up...");
      });
    

    Step 5: Paste the following into your crud.ts file

import {Router, Request, Response} from "express";
import { contactsCollection } from "./crudModel";

const routes = Router();

routes.post("/contact", async (req: Request, res: Response) => {
    const {fullName, phoneNumber} = req.body;

    const contact = await contactsCollection.create({fullName, phoneNumber});

    res.status(201).send({
        message: "New contact created successfully",
        data: contact
    });
});

routes.get("/contacts", async (req: Request, res: Response) => {
    const {fullName, phoneNumber} = req.body;

    const contact = await contactsCollection.find({});

    res.send({
        message: "All contact retrieved successfully",
        data: contact
    });
});

routes.get("/contacts/:id", async (req: Request, res: Response) => {
    const {id} = req.params;

    const contact = await contactsCollection.findById(id);

    res.send({
        message: "Contact retrieved successfully",
        data: contact
    });
});

routes.get("/contacts/:phoneNumber", async (req: Request, res: Response) => {
    const {phoneNumber} = req.params;

    const contact = await contactsCollection.findOne({phoneNumber});

    res.send({
        message: "Contact retrieved successfully",
        data: contact
    });
});

routes.put("/contacts/:id", async (req: Request, res: Response) => {
    const {id} = req.params;

    const {fullName, phoneNumber} = req.body;

    const contact = await contactsCollection.findByIdAndUpdate(id, {
        fullName, phoneNumber
    }, {new: true});

    res.send({
        message: "Contact updated successfully",
        data: contact
    });
});

routes.patch("/contacts/:id", async (req: Request, res: Response) => {
    const {id} = req.params;

    const {fullName} = req.body;

    const contact = await contactsCollection.findByIdAndUpdate(id, {
        fullName
    }, {new: true});

    res.send({
        message: "Contact updated successfully",
        data: contact
    });
});

routes.delete("/contacts/:id", async (req: Request, res: Response) => {
    const {id} = req.params;


    const contact = await contactsCollection.findByIdAndDelete(id);

    res.send({
        message: "Contact deleted successfully",
        data: contact
    });
});

export default routes;

Step 6: Paste the following code into your crudModel.ts file

import {Schema, model} from "mongoose";

const myContactsSchema = new Schema({
    fullName: {
        type: String,
        required: true
    },
    phoneNumber: {
        type: String,
        required: true
    }
}, {timestamps: true});

const contactsCollection = model("contacts", myContactsSchema);

export {
    contactsCollection
}

Step 7: Modify your package.json file

"scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "start": "nodemon index.js"
  }

Step 8: Open up your terminal and type npm start. This should startup your server automatically. The result should look like this:

Step 9: Test the API.

You could use postman to test the API, but a more convenient (IMO) option would be to use rest client, so in your root folder, create a test.rest file and paste the following into it:

POST http://localhost:4000/crud/contact
Content-Type: application/json

{
    "fullName": "Jimmy wire wire",
    "phoneNumber": "081122222211111"
}

###

GET http://localhost:4000/crud/contacts
Content-Type: application/json

###

GET http://localhost:4000/crud/contacts/676f654d7872dbd2f49aee48
Content-Type: application/json

###

PUT http://localhost:4000/crud/contacts/676f654d7872dbd2f49aee48
Content-Type: application/json

{
    "fullName": "Victor Ukok",
    "phoneNumber": "0811111111211"
}

###

PATCH http://localhost:4000/crud/contacts/676f654d7872dbd2f49aee48
Content-Type: application/json

{
    "fullName": "Victor Ukok edited"
}

###

DELETE http://localhost:4000/crud/contacts/676f6638b3383ee1f50955f1

The result should look like this:

Image description

Conclusion

This API can be deployed on platforms like render.com , documented on postman and made available to the public if you want to take it to that extent. I hope you learnt about how to conduct CRUD operations using APIs? Hit me up if you have any question/questions.