Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 47

Building RESTful APIs: Handling HTTP Methods

Recommended resources

Hello! Welcome back to our journey into backend development.

In our last lesson, you successfully set up your first Node.js project and created a basic web server with Express. You have a server that's running and can respond to a simple GET request at the root URL. That's a fantastic start! However, real-world APIs need to do much more than just say "Hello World."

Today, we're going to build on that foundation to create a more functional API. Your goal for this lesson is to implement API endpoints for the core HTTP methods: GET, POST, PUT, and DELETE. These methods are the fundamental building blocks for almost any web service, allowing clients (like the React apps you've built) to create, read, update, and delete data. This set of operations is commonly referred to as CRUD.

Understanding REST and CRUD

Before we write code, it's important to understand the convention we'll be following: REST (Representational State Transfer). REST is an architectural style for designing networked applications. It uses standard HTTP methods to perform actions on resources. A "resource" is any piece of information that can be named—in our case, it will be a collection of "courses."

The mapping between HTTP methods and CRUD operations is straightforward:

Operation CRUD HTTP Method Description
Create Create POST Add a new resource.
Read Read GET Retrieve one or more resources.
Update Update PUT Replace an existing resource entirely.
Delete Delete DELETE Remove a resource.
Exploring HTTP Methods in Node.js
This graphic visually introduces the common HTTP methods we will be implementing in our Node.js application.

To get a clear and concise explanation of these concepts, the following video by Programming with Mosh is an excellent starting point.

How to build a REST API with Node js & Express

Watch this clip from 'How to build a REST API with Node js & Express' to understand what REST is and how the different HTTP methods map to the CRUD operations we'll be building.

Watch from 00:39 to 06:24. Focus on how a resource (like 'customers') is exposed via a URL, and how GET, POST, PUT, and DELETE are used to interact with it.


Setting the Stage: Our In-Memory "Database"

To keep our focus on Express and API endpoints, we won't connect to a real database just yet. Instead, we'll use a simple array in our code to store our data. This is a common practice for learning and prototyping.

Let's modify your index.mjs (or index.js if you are not using modules) file from the last lesson. We'll start by defining an array of courses.

Replace the content of your main server file with this initial setup:

import express from 'express';

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

// Our in-memory database
const courses = [
    { id: 1, name: 'JavaScript Core Concepts' },
    { id: 2, name: 'Interactive Frontend with the DOM' },
    { id: 3, name: 'Asynchronous JavaScript and APIs' },
];

// We will add our routes here...

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

Make sure your server is running with npm run start:dev so it will auto-restart as we make changes.


1. Implementing READ Operations (GET)

The GET method is used to retrieve data. We'll implement two GET endpoints: one to get all courses and another to get a specific course by its ID.

Get All Courses

To get the full list of courses, we'll create an endpoint at /api/courses. When a client sends a GET request to this URL, our server should respond with the entire courses array.

Add the following code to your server file:

// GET all courses
app.get('/api/courses', (req, res) => {
    res.send(courses);
});

Now, you can test this! Open your browser and navigate to http://localhost:3000/api/courses. You should see the array of courses displayed as a JSON string.

Get a Single Course

To get a single course, we need a way to specify which course we want. We do this with a route parameter. A route parameter is a named segment of the URL, captured by Express so you can use it in your code. We'll define it with a colon, like :id.

Add this endpoint to handle requests for a single course:

// GET a single course by ID
app.get('/api/courses/:id', (req, res) => {
    // Find the course in our array
    const course = courses.find(c => c.id === parseInt(req.params.id));

    // If the course doesn't exist, send a 404 Not Found error
    if (!course) {
        return res.status(404).send('The course with the given ID was not found.');
    }

    // Otherwise, send the course data
    res.send(course);
});

Key Concepts:

  • req.params.id: This is how Express gives you access to the id part of the URL (e.g., if the URL is /api/courses/2, req.params.id will be "2").
  • parseInt(): Route parameters are always strings, so we must convert the ID to a number to compare it with the id in our courses array.
  • res.status(404): If we can't find the course, it's a best practice to send a 404 Not Found status code. The return statement is important to stop the function from executing further.

Test this by navigating to http://localhost:3000/api/courses/1 (should show the first course) and then http://localhost:3000/api/courses/99 (should show the 404 message).


2. Implementing the CREATE Operation (POST)

The POST method is used to create a new resource. To do this, the client needs to send the data for the new course (e.g., its name) in the request body.

By default, Express doesn't know how to read the JSON data from a request body. We need to enable it by using a built-in piece of middleware: express.json().

Add this line near the top of your file, right after const app = express();:

app.use(express.json()); // This middleware is crucial for parsing JSON request bodies

Now, let's implement the POST route:

// CREATE a new course
app.post('/api/courses', (req, res) => {
    // Basic validation: check if the name is provided and is long enough
    if (!req.body.name || req.body.name.length < 3) {
        return res.status(400).send('Name is required and should be minimum 3 characters.');
    }

    const newCourse = {
        id: courses.length + 1, // Simple ID generation
        name: req.body.name,
    };
    
    courses.push(newCourse);
    res.status(201).send(newCourse); // Send back the newly created course with a 201 status
});

Key Concepts:

  • app.use(express.json()): This tells Express to automatically parse any incoming request that has a Content-Type of application/json.
  • req.body: The parsed JSON data from the request is made available on the req.body object.
  • res.status(400): If the input is invalid, we send a 400 Bad Request status.
  • res.status(201): A successful creation is typically indicated with a 201 Created status code.

Testing POST, PUT, and DELETE:
You can't test these methods directly in a browser's address bar. You need an API client. I recommend using the Thunder Client extension for VS Code, or a standalone tool like Postman.

To test your POST endpoint:

  1. Open your API client.
  2. Set the method to POST.
  3. Set the URL to http://localhost:3000/api/courses.
  4. Go to the "Body" tab and select "JSON".
  5. Enter your new course data: { "name": "React Fundamentals" }.
  6. Send the request. You should get a 201 Created response with your new course object.

3. Implementing the UPDATE Operation (PUT)

The PUT method is used to update an existing resource. This endpoint will combine what we learned from GET /:id and POST. We need to find the course by its ID and update it with data from the request body.

// UPDATE an existing course
app.put('/api/courses/:id', (req, res) => {
    // 1. Look up the course
    const course = courses.find(c => c.id === parseInt(req.params.id));
    if (!course) {
        return res.status(404).send('The course with the given ID was not found.');
    }

    // 2. Validate the input
    if (!req.body.name || req.body.name.length < 3) {
        return res.status(400).send('Name is required and should be minimum 3 characters.');
    }

    // 3. Update the course
    course.name = req.body.name;
    res.send(course); // Return the updated course
});

To test this, use your API client:

  1. Set the method to PUT.
  2. Set the URL to an existing course, e.g., http://localhost:3000/api/courses/3.
  3. In the JSON body, provide the new data: { "name": "Async JS & APIs - Updated" }.
  4. Send the request. You should get a 200 OK response with the updated object.

4. Implementing the DELETE Operation (DELETE)

Finally, DELETE is used to remove a resource. This is the most straightforward of the "write" operations.

// DELETE a course
app.delete('/api/courses/:id', (req, res) => {
    // 1. Look up the course
    const course = courses.find(c => c.id === parseInt(req.params.id));
    if (!course) {
        return res.status(404).send('The course with the given ID was not found.');
    }

    // 2. Delete the course
    const index = courses.indexOf(course);
    courses.splice(index, 1);

    // 3. Return the deleted course
    res.send(course);
});

To test this:

  1. Set the method to DELETE.
  2. Set the URL to an existing course, e.g., http://localhost:3000/api/courses/2.
  3. Send the request. You should get back the course object that was just deleted.
Test your understanding!

What would happen if you tried to DELETE the same course twice? What status code would you expect on the second attempt, and why?

Show answer

On the first attempt, the course would be found and deleted, and the server would respond with a 200 OK status and the deleted course object.

On the second attempt, the find method would fail to locate the course because it's no longer in the array. The if (!course) condition would be true, and the server would respond with a 404 Not Found status. This is the correct and expected behavior.

For a full walkthrough of implementing these methods, you can refer to this comprehensive tutorial. It covers the same ground we just did and can help solidify your understanding.

Introduction to Building a CRUD API with Node.js and Express

This blog post from Harness, 'Introduction to Building a CRUD API with Node.js and Express,' provides excellent written explanations and code snippets for each of the CRUD operations. It's a great text-based resource to complement the video material.

First, review the section 'Designing the API Endpoints' to reinforce the RESTful principles. Then, read through 'Implementing CRUD Operations' to see the code for Create, Read, Update, and Delete, just as we have done.


Conclusion

Excellent work! You have now built a complete, albeit simple, REST API. You've gone from a server that could only handle one request to one that supports the full range of CRUD operations.

Key Takeaways:

  • RESTful APIs use standard HTTP methods (GET, POST, PUT, DELETE) to perform CRUD operations on resources.
  • app.get(), app.post(), app.put(), app.delete() are the Express methods for defining routes for each HTTP verb.
  • Route parameters (e.g., /:id) are used to identify specific resources, accessed via req.params.
  • The request body is used to send data for creating or updating resources, accessed via req.body after using the express.json() middleware.
  • HTTP status codes (200, 201, 400, 404) are essential for communicating the result of an API call back to the client.

Preview of the Next Lesson:
In this lesson, you used the express.json() middleware to parse the request body. Middleware is a powerful concept in Express that lets you run code on every request, or on requests to specific routes. In the next lesson, we will dive deeper into this topic and learn how to parse incoming JSON and URL-encoded request bodies using Express middleware more formally, and even write our own.