Build Your First REST API with MERN

Build Your First REST API with MERN

Are you a beginner looking to create your first RESTful API using the MERN stack? Look no further! In this beginner-friendly tutorial, I will guide you through the process of building a Todo App API using Node.js, Express.js, and MongoDB. By the end of this tutorial, you'll have a solid understanding of RESTful API concepts and be able to create your own APIs.

Let's get started!

What is a REST API?

REST stands for Representational State Transfer is an architectural style that defines a set of principles for designing networked applications. It provides a standardized way to interact with web services using the HTTP protocol. Let's break down the key components and principles of a RESTful API.

  • Resources: In a RESTful API, resources represent the entities or objects that the application manages. They can be anything from users, and products, to todos. Each resource has a unique identifier (ID) and attributes.

  • Endpoints: Endpoints are the URLs that clients use to access and manipulate resources. They define the path and HTTP method required to perform specific operations on resources.

  • HTTP Methods: RESTful APIs use HTTP methods to perform different actions on resources. The commonly used methods are:

    • GET: Retrieve a resource or a list of resources.

    • POST: Create a new resource.

    • PUT: Update an existing resource.

    • DELETE: Delete a resource.

Using these principles, we can design APIs that are intuitive, scalable, and easy to work with.

Setting up the Backend

To get started, let's set up the backend using Node.js, Express.js, and MongoDB. Follow the steps below:

Backend Setup

  1. Install Node.js and npm (Node Package Manager) on your machine.

  2. Create a new directory for your project and navigate to it using the command line.

  3. Initialize a new Node.js project by running npm init and following the prompts.

  4. Create a file named server.js in the root directory of your project.

     mkdir <project_name>
     cd <project_name>
     npm init
    

Installing Dependencies

To work with Node.js and Express.js, we need to install the necessary dependencies. Open your command line and run the following command:

npm install express mongoose

This will install Express.js and Mongoose (an Object Data Modeling library for MongoDB) in your project.

Setting up the Server

In this tutorial, we will use a Todo App as an example to demonstrate REST API concepts. The Todo resource represents a to-do item with attributes such as title, description, and completion status. We will implement CRUD operations (Create, Read, Update, Delete) for Todos.

Before that, you need to do some initial server configuration and make a connection with the database(MongoDB).

// ---------------SECTION 1-----------------
//Import necessary modules
const express = require('express');
const mongoose = require('mongoose');

//----------------SECTION 2-----------------
// Create an instance of Express
const app = express();

//----------------SECTION 3-----------------
// Connect to MongoDB (replace `your-mongodb-url` with your actual MongoDB connection URL)
mongoose.connect('your-mongodb-url', { 
    useNewUrlParser: true, 
    useUnifiedTopology: true 
})
  .then(() => {
    console.log('Connected to MongoDB');
  })
  .catch(error => {
    console.error('Failed to connect to MongoDB:', error);
  });

//----------------SECTION 4-----------------
// Middleware to parse JSON requests
app.use(express.json());


//all the api routes should be added here


//----------------SECTION 5-----------------
// Start the server
app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

I have divided the above code into multiple sections to make you understand each section individually.

Section 1: We import the required modules. express is a Node.js web application framework that helps us build server-side applications, and mongoose is an Object Data Modeling (ODM) library for MongoDB, which allows us to interact with the database.

Section 2: Here, we create an instance of the Express application. We assign it to the variable app, which represents our Express server.

Section 3: Here, we establish a connection to the MongoDB database using the mongoose.connect() method. You need to replace 'your-mongodb-url' with the actual URL of your MongoDB database.

The { useNewUrlParser: true, useUnifiedTopology: true } options are used to ensure that the connection is established properly.

The .then() method is a promise-based callback that executes when the connection is successful. It logs a message saying "Connected to MongoDB".

If there's an error during the connection process, the .catch() method is called, and it logs an error message.

Section 4: This code sets up a middleware using app.use(). Middleware functions are functions that have access to the request (req) and response (res) objects. They can perform certain tasks before passing the request to the next middleware or route handler.

Here, we use the express.json() middleware, which is responsible for parsing JSON data from incoming requests. It automatically parses the request body and makes it available as req.body.

Parsing refers to the process of converting data from one format to another. Specifically, express.json() middleware is responsible for parsing JSON data from incoming HTTP requests.

When a client sends a request to the server, the request body may contain JSON data. However, when the server receives the request, the data is typically in the form of a string. Parsing JSON involves converting this string representation of JSON data into a JavaScript object that can be easily accessed and manipulated in the server code.

Section 5: Finally, we start the server and make it listen on port 3000 using app.listen(). When the server starts successfully, it logs a message saying "Server listening on port 3000". Logically this section should be present at the bottom of the server.js file.

This allows the server to receive incoming HTTP requests and handle them based on the defined routes and logic.

That's it! This code sets up the basic configuration for a MERN stack application's backend. It creates an instance of Express, establishes a connection to MongoDB, sets up middleware for JSON parsing, and starts the server to listen for incoming requests.

Create Todo Model

By creating the Todo model, we can perform CRUD (Create, Read, Update, Delete) operations on the "todos" collection using the methods provided by Mongoose. This includes creating new todo items, fetching existing todo items, updating todo items, and deleting todo items.

" To organize the code, you can create a new file called "TodoModel.js" or any other appropriate name. In that file, you would write the below code snippet to define the Todo model. Then, in other parts of your code, you can import the Todo model and use it to interact with the "todos" collection in the database. "

//Import mongoose
const mongoose = require('mongoose');

// Define the Todo model schema
const todoSchema = new mongoose.Schema({
  title: String,
  description: String,
  completed: Boolean
});

// Create the Todo model
const Todo = mongoose.model('Todo', todoSchema);

Here, we use the mongoose.Schema() method to define the schema for the Todo model. A schema is a blueprint that defines the structure of documents (data) within a MongoDB collection. In this case, the Todo schema specifies that a Todo document should have three fields: title (of type String), description (of type String), and completed (of type Boolean).

After defining the schema, we create the Todo model using the mongoose.model() method. The model acts as an interface to interact with the MongoDB collection named "todos". The first argument 'Todo' specifies the singular name for the collection, and the second argument todoSchema is the schema we defined earlier.

Implement CRUD Operations

Create Todo

To create a new todo item, the client sends a POST request to the /todos endpoint with the necessary data in the request body. On the server side, we extract the data from the request and save it in the database.

// Create a new todo item
app.post('/todos', async (req, res) => {
  try {
    const { title, description, completed } = req.body;

    // Create a new todo item using the provided data
    const newTodo = new Todo({
      title,
      description,
      completed
    });

    // Save the todo item to the database
    const savedTodo = await newTodo.save();

    res.status(201).json(savedTodo);
  } catch (error) {
    console.error('Failed to create todo:', error);
    res.status(500).json({ error: 'Failed to create todo' });
  }
});

Inside the try block, the code extracts the title, description, and completed values from the request body. These values likely represent the data for the new todo item that the client wants to create.

  • The code then creates a new instance of the Todo model or database collection. It assigns the extracted values to the corresponding fields of the new todo item using object shorthand notation.

  • Next, the code calls await newTodo.save() to save the newly created todo item to the database. The save() method is likely provided by an ODM or ORM library and handles the actual saving process.

  • Once the todo item is successfully saved, the code responds with a 201 status code (indicating successful creation) and a JSON object containing the saved todo item.

  • If an error occurs during the creation or saving process, the code jumps to the catch block. It logs an error message to the console using console.error() and responds with a 500 status code and a JSON object containing an error message: { error: 'Failed to create todo' }.

Read Todo

To retrieve a list of all todo items, the client sends a GET request to the /todos endpoint. On the server side, we query the database for all todos and return them as a JSON response.

// Retrieve all todo items
app.get('/todos', async (req, res) => {
  try {
    const todos = await Todo.find();
    res.json(todos);
  } catch (error) {
    console.error('Failed to retrieve todos:', error);
    res.status(500).json({ error: 'Failed to retrieve todos' });
  }
});

Here, app refers to an instance of an Express application, which is a popular framework for building web applications in Node.js. The .get() function is used to define a route handler for the GET HTTP method.

Now let's move on to the code inside the route handler:

  • This code is wrapped in a try-catch block, which is a way to handle and manage errors. It helps us handle any potential errors that might occur during the execution of the code.

  • Inside the try block, the code attempts to retrieve the todo items using await Todo.find(). The await keyword is used to wait for the Todo.find() function to complete before proceeding further. Todo likely refers to a model or a database collection representing todo items.

  • The Todo.find() function is responsible for querying the database to retrieve all the todo items. It may use an Object-Document Mapper (ODM) or Object-Relational Mapper (ORM) library like Mongoose or Sequelize to interact with the database.

  • Once the todo items are retrieved successfully, the code sends a JSON response using res.json(todos). It means that the server will respond with the retrieved todo items in JSON format.

If an error occurs during the retrieval process, the code jumps to the catch block. In this case, it logs an error message to the console using console.error() and sends a 500 status code response along with a JSON object containing an error message.

Update Todo

To update an existing todo item, the client sends a PUT request to the /todos/:id endpoint with the ID of the todo item to be updated. On the server side, we find the todo item in the database, update its attributes, and save the changes.

// Update a todo item
app.put('/todos/:id', async (req, res) => {
  try {
    const { title, description, completed } = req.body;
    const todoId = req.params.id;

    const updatedTodo = await Todo.findByIdAndUpdate(
      todoId,
      { title, description, completed },
      { new: true }
    );
  } catch (error) {
    console.error('Failed to update todo:', error);
    res.status(500).json({ error: 'Failed to update todo' });
  }
});

Similar to the previous example, app refers to an instance of an Express application, and the .put() function is used to define a route handler for the PUT HTTP method.

Now let's dive into the code inside the route handler:

  • This code is also wrapped in a try-catch block to handle errors that may occur during its execution.

  • Inside the try block, the code extracts the values of title, description, and completed from the request body. The request body contains data that the client sends to the server when making a request. In this case, it likely contains the updated values for the todo item.

  • The req.params.id part retrieves the ID parameter from the URL. The :id part of the route path indicates a placeholder for the ID value. So, if the endpoint is '/todos/123', req.params.id would be equal to 123.

Next, the code uses await Todo.findByIdAndUpdate() to find and update the todo item in the database. The Todo.findByIdAndUpdate() function is commonly provided by an ODM or ORM library. It takes three parameters: the ID of the todo item to update, an object containing the new values for the fields (title, description, completed), and an options object { new: true } to ensure that the updated document is returned.

Delete Todo

To delete a todo item, the client sends a DELETE request to the /todos/:id endpoint with the ID of the todo item to be deleted. On the server side, we find the todo item in the database and remove it.

app.delete('/todos/:id', async (req, res) => {
  try {
    const todoId = req.params.id;

    const deletedTodo = await Todo.findByIdAndRemove(todoId);

    if (deletedTodo) {
      res.json({ message: 'Todo deleted successfully' });
    } else {
      res.status(404).json({ error: 'Todo not found' });
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to delete todo' });
  }
});

Inside the try block, the code retrieves the todoId from the request parameters. It extracts the ID from the URL using req.params.id.

  • Then, it uses await Todo.findByIdAndRemove(todoId) to find and remove the corresponding todo item from the database. The Todo.findByIdAndRemove() function is typically provided by an ODM or ORM library. It takes the ID of the todo item as a parameter and performs the deletion operation.

  • If a deletedTodo is returned (meaning a todo item was found and successfully deleted), the code responds with a JSON object containing a success message: { message: 'Todo deleted successfully' }.

  • If the deletedTodo value is null, it means that no matching todo item was found with the provided ID. In this case, the code responds with a 404 status code and a JSON object containing an error message: { error: 'Todo not found' }.

  • If any error occurs during the deletion process, the code jumps to the catch block. It responds with a 500 status code and a JSON object containing an error message: { error: 'Failed to delete todo' }.

Conclusion

Congratulations! You have successfully built a RESTful API for your MERN Todo App. We covered the fundamental concepts of REST and explained how to implement CRUD operations for the Todo resource using Node.js, Express.js, and MongoDB.

Remember, this blog only scratches the surface of what you can achieve with a RESTful API. You can further enhance your Todo App by adding features like user authentication, sorting, filtering, and pagination. Don't hesitate to explore additional resources and dive deeper into the world of RESTful APIs.

Now that you have a solid foundation, feel free to experiment, learn, and build amazing web applications powered by RESTful APIs.

Happy coding!