From Zero to CRUD Hero: Building Your First Backend API in JavaScript
Tags: webdev, javascript, node, api
Published: 2024-04-23 07:30:00

Application Programming Interface embed also known as API is the fundamental concept for interacting with data stored in a database. The process of setting it up can be time-consuming and difficult without adequate guidance. As a developer, you must understand how to efficiently develop CRUD (Create, Read, Update, and Delete) API for web applications.

For a developer to build a robust and flexible API for applications, the ability to set up routes, handle HTTP requests, send back HTTP responses, connect to the MongoDB database, and implement CRUD operations by leveraging on the capabilities of Node.js, Express.js, and MongoDB is needed.

This tutorial will walk you through the step-by-step process of building a RESTful CRUD API, from setting up the project development environment to connecting with the MongoDB database, then defining a data model with Mongoose which will interact with the MongoDB database cluster, then implementing the CRUD functionalities using Express routes and finally testing the API endpoints. You will also learn how to handle validations and errors. Upon completing this tutorial, you will be able to build dynamic CRUD APIs with efficient and safe data management capabilities.

## What is Node.js
Node.js is a cross-platform runtime environment that runs JavaScript code on the server rather than in the browser. Node.js helps to create scalable and high-performance applications.

## What is Express.js
Express.js is a Node.js framework that provides a robust set of features for developing Web APIs. It integrates with Node.js, making the creation of APIs simpler.

## Architecture
The API will be built using a three-layer architecture based on Node.js, Express.js, and MongoDB.
  
![CRUD API Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ms1mhau4voc43c2gxma5.png)

The **Data Access Layer** is where we'll communicate and interact with our database. 

Inside the **Controller layer**, we will handle the entire business logic that is associated with our endpoint's HTTP requests and responses.

The **Router layer** will send URLs to the appropriate controller functions. The router functionality will be obtained from Express.
Our API architecture for building CRUD APIs is straightforward, simple, and easy to understand.

## What is CRUD API?
Since the beginning of this article, we have been hearing the term CRUD API. Let's take a closer look at it now.

A CRUD API refers to the basic operations that can be performed on the data in a database to enable it to interact with the application. They are used as building blocks in web development to create applications that require data persistence and manipulation.

So, the phrase CRUD refers to the ability to CREATE, READ, UPDATE, and DELETE entities from a database.

## Understanding Each Functionality/Operation
**CREATE:** This functionality allows the creation of new data in the database.

**READ:** This functionality allows the retrieval of existing data from the database.

**UPDATE:** This functionality enables modification of existing data that is already in the database.

**DELETE:** This functionality allows the removal of existing data that is no longer wanted from the database.

## Prerequisite
You do not need to be an expert to follow along with this tutorial, but you will need the following: 

1. Solid understanding of JavaScript 
2. Knowledge of Node.js and Express.js. 
3. Download and install Node and Postman on your machine. 
4. Download and install the VSCode text editor on your computer.

## Setting Up Application Development Environment
This tutorial will use VSCode, but any text editor should work. To begin, let's create our development environment. We will not complicate the setup but rather create a simple and orderly project structure for our RESTful CRUD API. To make things easier, we'll split the following sections into two:

1. Setting up the project and installing dependencies.
2. Creating the Basic Express Server.

### Setting up the project and installing dependencies 
By typing the following command in our command prompt or command line interface, we can construct the overall project structure, including all relevant folders and files.

#### Navigating to the Desktop
`cd Desktop` 
#### Create a project folder and navigate into it
`mkdir book-management-api && cd book-management-api`
#### Create a src folder and navigate into it
`mkdir src && cd src`
#### Create subfolders in the src folder
`mkdir model && mkdir controllers && mkdir routes && mkdir db`
#### We need to move one level up since we are in the src folder
`cd ..`
#### Create an entry point for our API in the root directory
`touch index.js`
#### Initialing our node app and creating a package.json file
`npm init –y`

When you run the above commands, your package.json file and application project structure should be produced. Now launch your project in VSCode using the command below.
`code .`

![1. Installing Node.js newly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xome9j3pc9fh9xge2ph.png)

Now that you've successfully set up your development environment, your `package.json` file should look like the above. Next, we'll add additional development dependencies to our project. To install, open a terminal and enter the following command.
```
npm install express dotenv mongoose
```
- Express: a flexible Node.js web app framework

- Dotenv: it loads environment variables from a .env file

- Mongoose: it is a MongoDB object modeling tool designed to work in an asynchronous environment.

After installing the essential dependencies, your updated `package.json` file should look like this:

![2. Successfully Installed dependencies](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gu00mmswgjxlxtytxozn.png)

### Creating the Basic Express Server
Now let's configure our web server. In the entry file `index.js`, add the following code:
```
require("dotenv").config();
const express = require("express");

const app = express();

app.get("/", (req, res) => {
  res.send("Hey! Sever is Up.");
});

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => {
  console.log(`Server Listening on Port ${PORT}`);
});
```
The code above accomplishes the following;
(i). We imported dotenv and used the **.config()** function to initialize it instantly. 
(ii). We imported Express and assigned it to a variable named app. This variable now has access to all express methods. 
(iii). Then we create a simple route **(/)** that sends **"Hey! Server is up."** We received a response when we accessed it. 
(iv). Finally, we listen for all incoming requests on port 5000, which we have set as our server origin.

Let’s start the server, go back to your terminal, and run this command:
#### To Start the server
`node index.js`
Once everything is completed, open your browser and navigate to http://localhost:5000; you should see "Hey! Server is up" displayed on the screen.

We have one drawback with the node index.js command we used before, it restarts our server every time the file is altered. To make it easier to execute our Node program, we'll add a new start script to our `package.json` file. Use the command below to install it:
```
npm install -D nodemon
```
Once installed, insert the following script into the `package.json` file:

```
 "scripts": {
    "start": "nodemon index.js"
  },
```
The package.json file should look like this:

![3. added Nodemon and its script](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t03d5dak4dbc9ptdcq1b.png)

Use the following command to run the server and verify that everything is working correctly:
```
npm run start
```
**NOTE:** Always move to the root directory before running the server.

The message in the terminal will indicate whether or not the above command has launched the Express server.

![4. server starting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kk3ae7y5c308eiigh6e4.png)

From what we can see above our express server is running. You will also see the image below if you go to the URL http://localhost:5000 in your browser

![5. using Nodemon to run server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy3xcrbnmkmvzq4x3aai.png)

Voila! The project is perfectly set up. In the next section, we will set up our MongoDB Atlas account, Database Cluster, and then retrieve our connection string from MongoDB.

## How to Connect a Node.js application to a MongoDB Database Cluster.
In this part of the tutorial, we'll connect our MongoDB database cluster to our Node application using the connection string and also configure our Node application to connect to our database. To make learning easier, we have divided this section into 3 parts:

1. Setting up MongoDB Atlas, setting up the database cluster, and retrieving the connection strings.
2. Configuring and Connecting the Node application to the Database.
3. Modifying the Entry File to connect to our Express Server.

### Setting up MongoDB Atlas, setting up database cluster, and retrieving the connection strings.

We will set up our MongoDB database, and then retrieve our connection string.

First, visit [MongoDB Atlas and create an account](https://www.mongodb.com/), or sign in if you already have one. This article will guide you through [the process of creating a MongoDB account](https://dev.to/zechariah17/mongodb-for-beginners-setting-up-your-database-with-atlas-4k53). You should be redirected to your dashboard once you have completed the process. Locate the Connect button and click it.

![7. mongoDB dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gncptq3u5zgs313q0o9j.png)

The Drivers option will appear in the modal, select it so we can connect to our application immediately. 

![8. drivers options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8fzrc9fe9azcn2dcu87.png)

Another modal will appear. Follow the steps below to set up your MongoDB driver and get your connection string:

1. Select the latest Node.js driver and version. 

2. Install the driver package with NPM and install MongoDB. 

3. Copy and save it somewhere. The connection string will be used to connect the node application to MongoDB. 

![9. connection string section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d162wd0kv24t5sw3jib1.png)

We've successfully set up our MongoDB Atlas account, configured our MongoDB Cluster, and obtained the connection string.

### Configuring and Connecting the Node application to the Database.
Create a `.env` file in the root directory. It will be used to securely store all of our application's sensitive information. Assign a value name to the connection string inside the `.env` file.
```
MONGO_URL=mongodb+srv://<databaseUsername>:<databasePassword>@nodeexp.4glzj53.mongodb.net/BOOK-LISTING-API?retryWrites=true&w=majority
```
**Note:** Replace the above-mentioned database username and password with your own.

Within the db folder we created earlier, create a new file named `connect.js`. You can accomplish this by opening up the terminal and entering the following command:
#### Move to the src folder and then move to the db folder as well
`cd src && cd db`
#### Create a connect.js file
`touch connect.js`
#### Navigate from the db folder and then move back to the root folder
`cd .. && cd ..`
Now, our Node application's folder structure should look like the image below, with the newly created file included.

![10. successfully created the connect.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idrf9h0lwz32vanco3r5.png)

Next, we need to define a method within the `connect.js` file that facilitates the connection between our Node.js application and the database. Include the following code in the `connect.js` file.
```
const mongoose = require("mongoose");

const connectDB = (url) => {
  return mongoose.connect(url);
};

module.exports = connectDB;
```
In the code above, we imported Mongoose and constructed a connected function that returns a Promise that can be resolved or rejected via the URL, using the Mongoose **connect()** method. URL represents the connection string that should be added to the `.env` file.

### Modifying the Entry File to connect our Express Server
In `index.js`, import the **connectDB** function at the top and construct a function that connects to our database and launches the server, which will listen for incoming requests once the connection is established.
```
const connectDB = require("./src/db/connect");

const start = async () => {
  try {
    await connectDB(process.env.MONGO_URL);
    app.listen(PORT, () => {
      console.log("Database & Node app connected Successfully");
    });
  } catch (error) {
    console.log(error);
  }
};

start();
```
Because our **connectDB** function returns a Promise, the preceding code uses async/await in its start function. The Express server will automatically begin listening for incoming requests once the asynchronous function is resolved and the database connection is established.

![11. server connected node to mongoDB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptgqdkw4eyflvwme1uzf.png)

After restarting the Node application, you should see the above message, which indicates that your database and application have been synchronized. Good on so far, Let's keep going.

## Creating a RESTful CRUD API.

Node JS uses the MVC (Model View Controller) architecture. It's a design pattern. Based on the idea, we will create the following API endpoints for our Book Management:

1. POST **/book** - Create or add a new book. 

2. GET **/book** - Retrieve all books available.
 
3. Get **/book/:id** - Retrieve a specific book.
 
4. PATCH **/book/:id** - Update a book. 

5. Delete **/book/:id** - DELETE a book.

In the first step, we will create the model, followed by the CRUD API operations which will store and retrieve all data in MongoDB.

### Creating a Model

A model is a fancy constructor derived from a schema. They create and read documents from the underlying MongoDB database. In other words, they form part of the development that represents and handles data. Models are the blueprints that organize our application's data. They describe what can be created, read, updated, and deleted as well as the structure, behavior, and actions that are allowed. We can define our data structure with Mongoose's schema.

Create a `Book.js` file within the model folder. To accomplish this, run the following commands:

#### Move to the src folder and then move to the db folder as well
`cd src && cd model`
#### Create a book.js file (To Define Model structure)
`touch Book.js`
#### Navigate from the db folder and then move back to the root folder
`cd .. && cd ..`

After executing the command to create the `Book.js` model file, our Node application's folder structure should look like the image below, with the newly created file included.

![12. Newly added Book.js file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90529k2broemrcszmbip.png)

Next, we'll create a simple book schema with its fields included. In the newly created `Book.js` file, include the following code:
```
const mongoose = require("mongoose");

const bookSchema = new mongoose.Schema({
  title: String,
  author: String,
  publicationYear: Number,
  isStared: Boolean,
});

const Book = mongoose.model("Book", bookSchema);

module.exports = Book;
```
With the code above we accomplish the following. 
(i). Imported the Mongoose library to communicate with MongoDB in our Node.js project. 
(ii). Used **mongoose.Schema()** to create the database schema structure for our book document in the MongoDB database. 
(iii). `Title, Author, PublicationYear, and isStared` indicate the specific fields in each book document. 
(iv). The **mongoose.model()** function creates a model based on the database schema structure we supplied. In addition, we exported the Book model so that it could be reused in other files.

### Building a CRUD API that uses MongoDB

Now that we have structured the schema of our API, let's start by talking about the two most critical components of a RESTful CRUD API: controllers and routes.

**Controllers** are responsible for processing data, making decisions, and orchestrating API flows. They receive requests from routes, interact with the model, and determine the response to give to the client. They include business logic for handling individual requests and generating responses.

**Route** functions are similar to maps, directing incoming requests to the proper location inside the application. They define which URLs clients can access and what happens when they are visited. Routes are paths users can follow to explore your application, and each route corresponds to a specific function or collection of functions that process requests and respond to them.

### How Controllers and Routes Work Together in Node.js Applications 

Routes serve as the entry point for receiving requests in a Node.js application. When the router receives the request, it determines which route should handle it depending on the URL and HTTP method.

Next, after identifying the right route, the router calls the controller function associated with it. A controller executes business logic by retrieving data from the request and using it to perform actions such as validating input, generating a response, and retrieving data from the database.

After processing the request, handling the business logic, and generating a response, the controller returns it to the router, which sends it back to the client. 

### Creating a Controller

Create a file called `bookControllers.js` in the src/controllers folder. To accomplish this, run the following commands:

#### Move to the src folder and then move to the db folder as well
`cd src && cd controllers`
#### Create a bookController.js file in the controllers folder
`touch bookControllers.js`
#### Navigate from the db folder and then move back to the root folder
`cd .. && cd ..`

After executing the command to create the `bookControllers.js` file, our Node application's folder structure should look like the image below, with the newly created file included.

![13. newly added book controller file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yd702a8xw2jmhsrryz9f.png)

Next, we need to add the following functions to `bookController.js`, which will function as a communication interface with the Book model, as well as a method to handle CRUD API business logic, as well as a method to forward responses to other parts of the application.
```
const Book = require('../model/Book');

const createBook = (req, res) => {};
const getAllBooks = (req, res) => {};
const getSingleBook = (req, res) => {};
const updateSingleBook = (req, res) => {};
const deleteBook = (req, res) => {};

module.exports = {
  createBook,
  getAllBooks,
  getSingleBook,
  updateSingleBook,
  deleteBook,
};
```
Let's examine each controller function in the `bookController.js` file one by one to see how it implements the business logic.

#### Logic 1: Create a book

Inside the entry file `index.js`, add the following built-in Express middleware function:
```
app.use(express.json());
```
The code above creates middleware that parses incoming JSON data from the request body and makes it available for your express application in `req.body`.

**Note:** This is required for managing JSON data in the application, such as processing data submitted via forms or API requests.

Next, open the `bookController.js` file. Using the Book model, we can create a document for every book in our database cluster.
```
const createBook = async (req, res) => {
  const book = await Book.create(req.body);
  res.status(201).json({book})
};
```
#### Logic 2: Retrieve all books.

Next, inside the `bookController.js` file. To access the complete list of books available in our API, use the **.find({})** function on the Book model.
```
const getAllBooks = async (req, res) => {
  const books = await Book.find({});
 res.status(200).json({books})
};
```
#### Logic 3: Retrieve a Single Book

Our next step will be to retrieve a single book from the database using its unique ID.
```
const getSingleBook = async (req, res) => {
  const { id: bookId } = req.params;
  const singleBook = await Book.findOne({ _id: bookId });
  res.status(200).json({ singleBook });
};
```
The code above accomplishes the following: 

(i). Assign the ID parameter from the request parameter object `req.params` to a variable called `bookId`. When you make a request, the URL will include the ID. 
(ii). The **Book.findOne()** method searches the database for a single document with the `_id` field matching the `bookId `from the request parameters.

#### Logic 4: Update a book

Next, we'll update a book document in the database.
```
const updateSingleBook = async (req, res) => {
  const { id: bookId } = req.params;
  const singleBook = await Book.findOneAndUpdate({ _id: bookId }, req.body, {
    new: true,
    runValidators: true,
  });
  res.status(200).json({ singleBook });
};
```
In the code above, the following is done: 
(i). The `const {id: bookId} = req.params` method extracts the id parameter from the request and applies it to the bookId variable. 
(ii). The **findOneAndUpdate()** method changes a single book document stored in the database. 
(iii). It searches for a book document using the `bookId` and changes it with data from the request body. 
(iv). While the `new:true` option returns the revised document, the `runValidators:true` option executes Mongoose validators.

#### Logic 5: Delete a book

Finally, removing a single book from the database depends on its ID. Add the code below to the bookController.js file.
```
const deleteBook = async (req, res) => {
  const { id: bookId } = req.params;
  const deleteBook = await Book.findOneAndDelete({ _id: bookId });
  res.status(200).json({ deleteBook });
};
```
The above code performs the following: 

(i). Assign the id parameter from the request parameter object `req.params` to a variable called `bookId`. 
(ii). The Book model's **findOneAndDelete()** method deletes a single book document with a matching `_id `field from the database.

We have developed all the business logic for the CRUD API operations we will be using. To generate the operations, we used the async and await keywords since the database query takes time, which is when the Node.js asynchronous attribute comes into play. The JSON response we return with each operation includes the status code as well as the book document stored in our database.

### Defining Routes

When a client sends a request for an endpoint via HTTP requests (GET, POST, PATCH, DELETE), we need to define how the server will respond by setting up the routes.

Create a file called `bookRoute.js` in the src/routes folder. To do this task, open your terminal and type the following commands:

#### Move to the src folder and then move to the db folder as well
`cd src && cd routes`
#### Create a bookRoutes.js file in the controllers folder
`touch bookRoutes.js`
#### Navigate from the db folder and then move back to the root folder
`cd .. && cd ..`

After executing the command to create the `bookRoutes.js` file, our Node application's folder structure should look like the image below, with the newly created file included.

![14. newly added book routes file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mseg2o07qqrvs5jfsz2o.png)

Next, we'll set up routes for each controller. Include the following code content:
```
const express = require("express");
const {
  createBook,
  getAllBooks,
  getSingleBook,
  updateSingleBook,
  deleteBook,
} = require("../controllers/bookControllers");
const router = express.Router();

router.post("/", createBook);
router.get("/", getAllBooks);
router.get("/:id", getSingleBook);
router.patch("/:id", updateSingleBook);
router.delete("/:id", deleteBook);

module.exports = router;
```
In the code snippet,
(i). Our API is built using the Express package, which provides the functionality that we need. 
(ii). Express Router is middleware that organizes application routes. The `const router = express.Router()` method is used to create a new router object using Express Router.
(iii). `const {...} = require("../controllers/bookControllers") `imports all business logic functions from bookControllers.js. 
(iv). There are four types of routes: router.post, router.get, router.patch, and router.delete, linked by URL patterns and controllers. 
(v). We export the router object for use in index.js.

Finally, we must register the `bookRoute.js` file in our entry file index.js so that any calls to endpoints are redirected to the correct URL path. To accomplish this, we'll add the following code to the index.js file:
```
const bookRouter = require("./src/routes/bookRoutes");
```
Use the **app.use()** function, specifying the path and router handler.
```
app.use("/api/v1/books", bookRouter);
```
When we access the URL http://localhost:5000/api/v1/books, the router is activated. It effectively functions as a filter, determining when a particular set of routes should be used. 

Here's the whole complete code for the `index.js` file:
```
require("dotenv").config();
const express = require("express");
const connectDB = require("./src/db/connect");
const bookRouter = require("./src/routes/bookRoutes");

const app = express();

app.use(express.json());

app.get("/", (req, res) => {
  res.send("Hey! Sever is Up.");
});

app.use("/api/v1/books", bookRouter);

const PORT = process.env.PORT || 3000;

const start = async () => {
  try {
    await connectDB(process.env.MONGO_URL);
    app.listen(PORT, () => {
      console.log("Database & Node app connected Successfully");
    });
  } catch (error) {
    console.log(error);
  }
};
start();
```
Congratulations! We have successfully integrated the MongoDB database into our Node.js application and built the CRUD API action. Before we test our API, let's add some basic validation and error handling to improve its reliability and usability.

## Basic Validation and Error Handling

Let's work on improving our CRUD API now that it has connected to our MongoDB cluster and we have set up CRUD API operations.

For any API, error handling and data validation are crucial to ensuring the API responds appropriately to incorrect inputs and unexpected scenarios, thus improving the user experience.

If a user tries to add a book without a title, author, or publication year, they will be unable to do so. Without effective validation, our database may contain incomplete data. When error validation and error handling are in place, users will be informed of what went wrong rather than left in the dark.

### Adding basic validation

As a first step, let's validate our Book model. Add basic validation to `Book.js` in the model folder.
```
const mongoose = require("mongoose");

const bookSchema = new mongoose.Schema({
  title: { type: String, required: [true, "Please Provide a Title"] },
  author: { type: String, required: [true, "Please Provide Author Name"] },
  publicationYear: {
    type: Number,
    required: [true, "Please Provide Publication Year"],
  },
  isStared: { type: Boolean, default: false },
});

const Book = mongoose.model("Book", bookSchema);

module.exports = Book;
```
Mongoose will produce an error if we create a book without a title, author, or publication year because those fields are required, and the error message will explain what went wrong.

### Implementing error handling

An effective error management process involves detecting errors and responding appropriately to them. To handle errors graciously, let's improve our `bookController.js` CRUD API action.

#### Create a Book with Error Handling
```
const createBook = async (req, res) => {
  try {
    const book = await Book.create(req.body);
    res.status(201).json({ book });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};
```

#### Retrieving All Books with Error Handling
```
const getAllBooks = async (req, res) => {
  try {
    const books = await Book.find({});
    res.status(200).json({ books });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};
```

#### Get a Single Book with Error Handling
```
const getSingleBook = async (req, res) => {
  try {
    const { id: bookId } = req.params;
    const singleBook = await Book.findOne({ _id: bookId });
    if (!singleBook) {
          return res.status(404).json({ msg: `No Book with Id ${bookId}` });
    }
    res.status(200).json({ singleBook });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};
```

#### Update the Book with Error Handling
```
const updateSingleBook = async (req, res) => {
  try {
    const { id: bookId } = req.params;
    const singleBook = await Book.findOneAndUpdate({ _id: bookId }, req.body, {
      new: true,
      runValidators: true,
    });
    if (!singleBook) {
          return res.status(404).json({ msg: `No Book with Id ${bookId}` });

    }
    res.status(200).json({ singleBook });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};
```

#### Delete a Book with Error Handling
```
const deleteBook = async (req, res) => {
  try {
    const { id: bookId } = req.params;
    const deleteBook = await Book.findOneAndDelete({ _id: bookId });
    if (!deleteBook) {
      return res.status(404).json({ msg: `No Book with Id ${bookId}` });
    }
    res.status(200).json({ deleteBook });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};
```
We implemented `try-catch blocks` in all of our CRUD API operations to ensure that every issue detected is handled appropriately. With these additions, our CRUD API now includes comprehensive error handling and validation capabilities. This not only makes our API more stable but also enhances the user experience by displaying clear and informative error messages.

Here is the complete code for the `bookController.js` file:
```
const Book = require("../model/Book");

const createBook = async (req, res) => {
  try {
    const book = await Book.create(req.body);
    res.status(201).json({ book });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};

const getAllBooks = async (req, res) => {
  try {
    const books = await Book.find({});
    res.status(200).json({ books });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};

const getSingleBook = async (req, res) => {
  try {
    const { id: bookId } = req.params;
    const singleBook = await Book.findOne({ _id: bookId });
    if (!singleBook) {
      return res.status(404).json({ msg: `No Book with Id ${bookId}` });
    }
    res.status(200).json({ singleBook });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};

const updateSingleBook = async (req, res) => {
  try {
    const { id: bookId } = req.params;
    const singleBook = await Book.findOneAndUpdate({ _id: bookId }, req.body, {
      new: true,
      runValidators: true,
    });
    if (!singleBook) {
      return res.status(404).json({ msg: `No Book with Id ${bookId}` });
    }
    res.status(200).json({ singleBook });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};

const deleteBook = async (req, res) => {
  try {
    const { id: bookId } = req.params;
    const deleteBook = await Book.findOneAndDelete({ _id: bookId });
    if (!deleteBook) {
      return res.status(404).json({ msg: `No Book with Id ${bookId}` });
    }
    res.status(200).json({ deleteBook });
  } catch (error) {
    res.status(400).json({ msg: error });
  }
};

module.exports = {
  createBook,
  getAllBooks,
  getSingleBook,
  updateSingleBook,
  deleteBook,
};
```
In the next section of the tutorial, we will test our CRUD API to ensure its stability and robustness. We are close to the finish line.

## Testing the CRUD API

After implementing error handling and validation, it is critical to test our CRUD API. Testing ensures that everything works as planned and detects any problems before they affect our users. Let's get started testing our CRUD API using Postman.

### Test each endpoint

- To create a Book API, send a POST request to http://localhost:3000/api/v1/books with a JSON body that includes the book title, author, and publication year.
 
![15. Testing Creating Book endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9bgumj5iqij3lwe0613.gif)

- Send a GET request to http://localhost:3000/api/v1/books to retrieve a list of all books available in the database.

![16. Testing all book endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqdt40s4j1psbyjulfu1.gif)

- To retrieve a single book, use the GET method and include the Book ID in the URL. If the Book ID is incorrect, it will return an error message; however, if the Book ID is correct, it will provide detailed information about that book. 

![17. Testing getting single book endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubxhdtlis4hb57k35s1x.gif)

- The content of a book can be updated by submitting a PACTH request with the new data for the specified Book ID. You will get an error message if the Book ID is incorrect. The new data should, however, be inserted correctly into the book.

![18. Testing updating book endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h3nxv5fvhn2oldpz187t.gif)

- To delete a book from the database, use the DELETE method on the book endpoint and enter the Book ID.

![19. Testing delete book endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmpg4571su0vafzzav46.gif)

Congratulations! We did it. After testing the CRUD API we created, they are now fully operational. The tutorial started with setting up our development environment and ended with building our API. That's an impressive achievement, and we should be proud of it.

## Conclusion

That was a fantastic ride. I enjoyed creating this piece of content for you and learned a lot in the process. Building a basic CRUD API from scratch is a difficult task that demands patience, perseverance, and the willingness to learn. Fortunately for me, you have shown all of these qualities and more.

In this tutorial, we looked at backend development by creating a simple Book CRUD API with Node.js and Express.js. This tutorial covered a wide range of topics, including setting up our development environment, creating our MongoDB Atlas account and retrieving our connection string, developing CRUD APIs using Express.js and Node.js, and testing them using Postman.

Congratulations on creating your CRUD API with Node.js and Express! Here's to many more coding adventures ahead. May the codes be with you!

If you could provide feedback, I would greatly appreciate it. Please post it in the comments section. If you find this tutorial helpful, please consider sharing it with others who might find it helpful too.