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.

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:
- Extract the user
idfrom the request parameters (req.params.id). - Call the
deleteUserService. - Check if a user was actually deleted. If
deleteUserServicereturnsundefined(because no user with that ID existed), we should send a404 Not Foundresponse. - 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:
- Use your
GET /api/usersendpoint to get a list of all users and pick anidyou want to delete. - 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.
- Send the request. You should get a
200 OKstatus with a message like "User deleted successfully" and the data of the deleted user.

Verification:
- Try to fetch the deleted user again by making a
GETrequest to/api/user/<ID>. You should now get a404 Not Founderror. - 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
DELETEHTTP 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
WHEREclause is non-negotiable to prevent accidental mass deletion of data. - Testing a
DELETEendpoint requires verification: ensuring the resource is truly gone after the request succeeds. - Destructive operations like
DELETEmust 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.