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. |

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 theidpart of the URL (e.g., if the URL is/api/courses/2,req.params.idwill be"2").parseInt(): Route parameters are always strings, so we must convert the ID to a number to compare it with theidin ourcoursesarray.res.status(404): If we can't find the course, it's a best practice to send a404 Not Foundstatus code. Thereturnstatement 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 aContent-Typeofapplication/json.req.body: The parsed JSON data from the request is made available on thereq.bodyobject.res.status(400): If the input is invalid, we send a400 Bad Requeststatus.res.status(201): A successful creation is typically indicated with a201 Createdstatus 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:
- Open your API client.
- Set the method to POST.
- Set the URL to
http://localhost:3000/api/courses. - Go to the "Body" tab and select "JSON".
- Enter your new course data:
{ "name": "React Fundamentals" }. - Send the request. You should get a
201 Createdresponse 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:
- Set the method to PUT.
- Set the URL to an existing course, e.g.,
http://localhost:3000/api/courses/3. - In the JSON body, provide the new data:
{ "name": "Async JS & APIs - Updated" }. - Send the request. You should get a
200 OKresponse 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:
- Set the method to DELETE.
- Set the URL to an existing course, e.g.,
http://localhost:3000/api/courses/2. - 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 viareq.params. - The request body is used to send data for creating or updating resources, accessed via
req.bodyafter using theexpress.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.