Skip to main content

Welcome to a Grasp lesson

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

Lesson 65

Implementing a DELETE Endpoint

Recommended resources

Welcome back! In our journey through building a RESTful API, you've successfully implemented endpoints for creating (POST), reading (GET), and updating (PUT/PATCH) user data. You now have a solid foundation for most of the data interactions an application needs.

In this lesson, we will complete the CRUD (Create, Read, Update, Delete) cycle by implementing the final, and most destructive, operation: Delete. Our learning outcome is to implement a DELETE endpoint to remove a record from the database. This is a straightforward but critical piece of functionality for managing data.

Let's get started!

The Anatomy of a DELETE Request

A DELETE request is used to remove a specific resource from the server. Like GET (by ID) and PUT, it targets a single resource using a unique identifier in the URL path.

The HTTP request is simple:

  • Method: DELETE
  • Endpoint: /api/user/:id

The endpoint includes the :id parameter to tell the server exactly which user to remove. Unlike POST or PUT, a DELETE request typically does not contain a request body.

DELETE REST Endpoint Example
This image illustrates the standard structure for a DELETE endpoint in an Express application. It clearly shows the `DELETE` method and the use of a route parameter (`:taskId`) to identify the resource to be deleted.

Step 1: The Service Layer and SQL DELETE Query

At the heart of this operation is the SQL DELETE command. Its syntax is:

DELETE FROM table_name WHERE condition;

Just as with the UPDATE command, the WHERE clause is absolutely critical. If you forget the WHERE clause, the database will delete every single row in your table. This is a catastrophic, unrecoverable action in most production systems.

Our deleteUserService function will take the id of the user to be deleted and execute this query. Using RETURNING * is also a good practice here, as it allows us to confirm exactly which record was removed.

Let's see how to write this service function.

Build a CRUD Rest API with Node.js, Express, PostgreSQL & Docker

In this clip from Dipesh Malvia's tutorial, you'll see how to create the deleteUserService function. The logic is concise and powerful.

Watch from 32:14 to 32:53. Focus on: The SQL query: DELETE FROM users WHERE id = $1. The RETURNING * clause: This returns the row that was just deleted. The parameterized id: The [id] array is passed to pool.query to safely inject the value and prevent SQL injection attacks.

Based on the video, your deleteUserService in src/models/user.js should look like this:

// src/models/user.js

// ... other service functions

export const deleteUserService = async (id) => {
  const result = await pool.query(
    'DELETE FROM users WHERE id = $1 RETURNING *',
    [id]
  );
  return result.rows[0];
};

Step 2: The Controller and Route

Next, we'll create the deleteUser controller. Its job is to:

  1. Extract the user id from the request parameters (req.params.id).
  2. Call the deleteUserService.
  3. Check if a user was actually deleted. If deleteUserService returns undefined (because no user with that ID existed), we should send a 404 Not Found response.
  4. If successful, send a confirmation response, often with the data of the user that was deleted.

Build a CRUD Rest API with Node.js, Express, PostgreSQL & Docker

Now, let's wire this up in the controller and the router.

First, watch from 38:01 to 38:39 to see the implementation of the deleteUser controller function in user.controller.js. Pay attention to how it handles the 'user not found' case. Then, add the corresponding route to your src/routes/userRoutes.js file, as shown around timestamp 24:38: router.delete('/user/:id', deleteUser);

Your deleteUser controller in src/controllers/userController.js will be:

// src/controllers/userController.js

// ... other controller functions

export const deleteUser = async (req, res, next) => {
  try {
    const { id } = req.params;
    const deletedUser = await deleteUserService(id);

    if (!deletedUser) {
      return handleResponse(res, 404, 'User not found');
    }

    handleResponse(res, 200, 'User deleted successfully', deletedUser);
  } catch (error) {
    next(error);
  }
};
Test your understanding!

Imagine you accidentally wrote your DELETE query as DELETE FROM users; without the WHERE id = $1 clause. What would be the immediate result when the deleteUser endpoint is called for the first time, and what would the result.rows[0] from the service function contain?

Show answer

The immediate result would be that all users are deleted from the table. This is a disastrous outcome.

The RETURNING * clause would return all the rows that were deleted. Therefore, result.rows would be an array containing every user object from the table before the deletion, and result.rows[0] would contain the first user in that array. However, the table itself would now be empty. This highlights the extreme importance of the WHERE clause.


Step 3: Testing the DELETE Endpoint

Testing a DELETE endpoint is a two-step process: you must not only confirm that the request was successful but also verify that the resource is actually gone.

Process:

  1. Use your GET /api/users endpoint to get a list of all users and pick an id you want to delete.
  2. In your API client (Thunder Client/Postman), create a new request:
    • Set the method to DELETE.
    • Set the URL to http://localhost:5001/api/user/<ID>, replacing <ID> with the chosen ID.
  3. Send the request. You should get a 200 OK status with a message like "User deleted successfully" and the data of the deleted user.
Successful DELETE Request in API Client
This image shows what a successful DELETE request looks like in an API client. Note the `200 OK` status and the confirmation message in the response body.

Verification:

  1. Try to fetch the deleted user again by making a GET request to /api/user/<ID>. You should now get a 404 Not Found error.
  2. Fetch all users with GET /api/users. The deleted user should no longer be in the list.

This two-part verification confirms your endpoint works correctly.

Build a CRUD Rest API with Node.js, Express, PostgreSQL & Docker

Let's watch the full testing and verification flow.

Watch from 46:51 to 47:22. The video demonstrates making the DELETE request and then verifying the deletion by attempting to get the user again, which correctly results in a 'User not found' error.


A Critical Note on Security

POST, PUT, and DELETE are all methods that modify data. DELETE is particularly sensitive because it causes permanent data loss. In a real-world application, you would never allow just anyone to delete resources. These endpoints must be protected.

This protection is called authorization—verifying that a user has the permission to perform an action. For example, only an administrator should be able to delete other users' accounts.

CRUD REST API with Node.js, Express, and PostgreSQL

The LogRocket article you've seen before has a great section on this topic. It highlights why securing endpoints like DELETE is so important.

Read the subsections under Securing the API, specifically Authentication and Authorization. This will give you a high-level understanding of the concepts we'll be diving into next.

We will build exactly this kind of protection in our upcoming modules.


Conclusion

Fantastic work! You have now built a complete, functional CRUD REST API from the ground up. You can create, read, update, and delete users in your PostgreSQL database using Node.js and Express. This is a foundational skill set for any backend or full-stack developer.

Key Takeaways:

  • The DELETE HTTP method is used to remove a resource, identified by a route parameter like :id.
  • The SQL command is DELETE FROM table_name WHERE condition.
  • The WHERE clause is non-negotiable to prevent accidental mass deletion of data.
  • Testing a DELETE endpoint requires verification: ensuring the resource is truly gone after the request succeeds.
  • Destructive operations like DELETE must be protected by authorization logic in a real application.

Preview of the Next Lesson:
With your CRUD API complete, our next major step is to secure it. In the next module, we'll begin our deep dive into Full-Stack User Authentication. The very first lesson will be to explain the token-based authentication flow using JSON Web Tokens (JWT). This will lay the theoretical groundwork for building the login and registration systems that protect sensitive endpoints like the one you just created.