Skip to main content
Node.js Basics
CHAPTER 18 Beginner

REST API Development with Express

Updated: May 13, 2026
30 min read

# REST API Development with Express

Welcome to Chapter 18! While rendering EJS templates is useful, the modern web relies heavily on a decoupled architecture.

Today, a typical company has a web app (built with React), an iOS app, and an Android app. If the backend sends EJS (HTML) to an iPhone app, the iPhone won't know what to do with it! Instead, the backend must send raw, structured data. This is where APIs (Application Programming Interfaces) come in. In this chapter, we will build a professional REST API.

---

1. Introduction

An API is a messenger that takes requests and tells a system what you want to do, then returns the response back to you.

REST (Representational State Transfer) is a set of rules for building APIs. A RESTful API relies on standard HTTP methods to perform CRUD operations:

  • Create -> POST
  • Read -> GET
  • Update -> PUT / PATCH
  • Delete -> DELETE

Instead of res.render() or res.send(), REST APIs almost exclusively respond using res.json().

---

2. Learning Objectives

By the end of this chapter, you will be able to:

  • Understand the principles of RESTful architecture.
  • Design standard resource endpoints (e.g., /api/users).
  • Build an API that performs all CRUD operations.
  • Send properly formatted JSON responses.
  • Understand HTTP Status codes in the context of APIs (200, 201, 400, 404).
  • Use express.json() middleware to parse incoming JSON data.

---

3. Beginner-Friendly Explanations

The REST Standard

Imagine an API managing "Books" in a library. REST dictates a standard way to structure URLs.
  • GET /api/books : Returns an array of *all* books.
  • GET /api/books/5 : Returns a single object for book ID 5.
  • POST /api/books : Accepts new data to create a book.
  • PUT /api/books/5 : Accepts data to update book ID 5.
  • DELETE /api/books/5 : Deletes book ID 5.

Notice how the URL path (/api/books) mostly stays the same. The action changes based on the HTTP Method used.

express.json() Middleware

When a client sends a POST request with new data (like a user registering), that data is attached to the "body" of the request. By default, Express doesn't know how to read JSON bodies. You must use the built-in app.use(express.json()) middleware to parse it and attach it to req.body.

---

4. Syntax Explanation

Let's look at handling an incoming POST request.

```javascript id="ch18-syntax-1" const express = require('express'); const app = express();

// CRITICAL: Middleware to parse incoming JSON payloads app.use(express.json());

// A POST route to create a user app.post('/api/users', (req, res) => { // We access the data sent by the client via req.body const newUser = req.body; if (!newUser.name) { // 400 Bad Request: The client forgot to send a name return res.status(400).json({ error: "Name is required" }); }

// (In a real app, we would save this to a database here)

// 201 Created: Respond with success res.status(201).json({ message: "User created successfully", user: newUser }); });

app.listen(3000);

1234567891011121314151617181920
**Output Explanation:**
If a React frontend sends a POST request to this endpoint with the JSON `{ "name": "Alice" }`, the server will respond with `{ "message": "User created...", "user": { "name": "Alice" } }`.

---

## 5. Real-world Examples

**Why JSON?**
JSON (JavaScript Object Notation) is the universal language of the web. It is lightweight and readable.
When you open a weather app on your phone, it makes a `GET` request to a weather API. The API responds with:
`{ "temp": 72, "conditions": "Sunny" }`
The mobile app reads this JSON and updates its graphical UI accordingly.

---

## 6. Multiple Code Examples

### Example 1: Mock Database Setup
Since we haven't learned MongoDB yet, we will use an array stored in memory as our "database".

javascript id="ch18-code-1" // Our temporary in-memory database let tasks = [ { id: 1, title: "Learn Express", completed: false }, { id: 2, title: "Build an API", completed: false } ];

1
### Example 2: READ (GET) endpoints

javascript id="ch18-code-2" // Get all tasks app.get('/api/tasks', (req, res) => { res.status(200).json(tasks); });

// Get a single task by ID app.get('/api/tasks/:id', (req, res) => { const taskId = parseInt(req.params.id); const task = tasks.find(t => t.id === taskId); if (!task) { return res.status(404).json({ error: "Task not found" }); } res.status(200).json(task); });

1
### Example 3: UPDATE (PUT) endpoint

javascript id="ch18-code-3" app.put('/api/tasks/:id', (req, res) => { const taskId = parseInt(req.params.id); const task = tasks.find(t => t.id === taskId); if (!task) { return res.status(404).json({ error: "Task not found" }); } // Update the task properties with data from req.body // (Assuming the client sends { "completed": true }) task.title = req.body.title || task.title; task.completed = req.body.completed !== undefined ? req.body.completed : task.completed; res.status(200).json({ message: "Task updated", task }); });

1
### Example 4: DELETE endpoint

javascript id="ch18-code-4" app.delete('/api/tasks/:id', (req, res) => { const taskId = parseInt(req.params.id); // Filter out the task we want to delete tasks = tasks.filter(t => t.id !== taskId); res.status(200).json({ message: "Task deleted successfully" }); });

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
---

## 7. Output Explanations

In a REST API, **HTTP Status Codes** are extremely important. They tell the client the result of the request without the client having to read the response message.
- `200` (OK): Success for GET, PUT, DELETE.
- `201` (Created): Success for POST.
- `400` (Bad Request): The client sent invalid data.
- `404` (Not Found): The resource doesn't exist.
- `500` (Server Error): Your code broke!

---

## 8. Common Mistakes

1. **Forgetting `app.use(express.json())`:** If you forget this middleware, `req.body` will be `undefined`, and your POST routes will crash when trying to access `req.body.title`.
2. **Not using `return` in error responses:** 
   ```javascript
   if (!task) {
       res.status(404).json({error: "Not found"}); 
   }
   res.json(task); // ERROR! Cannot set headers after they are sent.
   ```
   Always `return res.status...` to stop the function from executing the code below it.
3. **Comparing Strings to Numbers:** `req.params.id` is a String. If your database IDs are Numbers, `req.params.id === dbId` will evaluate to `false`. Always `parseInt(req.params.id)`.

---

## 9. Best Practices

- **Consistent Enveloping:** Wrap your responses consistently. If successful, send `{ data: [...] }`. If error, send `{ error: "message" }`.
- **API Versioning:** Prepend your routes with `/api/v1/`. For example: `/api/v1/users`. If you drastically change your API 2 years from now, you can create `/api/v2/users` without breaking old mobile apps that still use `v1`.

---

## 10. Exercises

1. Set up an Express app and enable `express.json()`.
2. Create an array of `movies` containing a few objects (`{id, title, rating}`).
3. Create a `POST /api/movies` route. It should take a new movie object from `req.body`, push it into the array, and return the newly created movie with a `201` status.

---

## 11. Mini Project: Todo API

**Objective:** Build a complete, functional RESTful API for a Todo list application.

**Code (`todoApi.js`):**

javascript id="ch18-mini-project" const express = require('express'); const app = express();

// Middleware app.use(express.json());

// In-Memory Database let todos = []; let nextId = 1;

// CREATE (POST) app.post('/api/todos', (req, res) => { if (!req.body.text) return res.status(400).json({ error: "Text is required" }); const newTodo = { id: nextId++, text: req.body.text, done: false }; todos.push(newTodo); res.status(201).json(newTodo); });

// READ ALL (GET) app.get('/api/todos', (req, res) => { res.status(200).json(todos); });

// UPDATE (PUT) app.put('/api/todos/:id', (req, res) => { const todo = todos.find(t => t.id === parseInt(req.params.id)); if (!todo) return res.status(404).json({ error: "Todo not found" });

todo.text = req.body.text || todo.text; if (req.body.done !== undefined) todo.done = req.body.done; res.status(200).json(todo); });

// DELETE (DELETE) app.delete('/api/todos/:id', (req, res) => { const initialLength = todos.length; todos = todos.filter(t => t.id !== parseInt(req.params.id)); if (todos.length === initialLength) { return res.status(404).json({ error: "Todo not found" }); } res.status(200).json({ message: "Todo deleted" }); });

app.listen(3000, () => console.log('Todo API running on port 3000')); ``

*(To test this, you must use Postman, Insomnia, or a frontend React app).*

---

12. Coding Challenges

Challenge 1: Add a PATCH route for /api/todos/:id/toggle. When hit, this route should simply find the todo and flip its done boolean (true becomes false, false becomes true) without requiring a req.body.

Challenge 2: Add a GET route /api/todos/status/completed that filters the array and returns *only* the todos where done === true.

---

13. MCQs with Answers

Q1: What does REST stand for? A) Reliable Engine State Transfer B) Representational State Transfer C) Reactive Server Transfer D) Routing Endpoint State Transfer Answer: B

Q2: Which Express middleware is absolutely required to read JSON data from req.body? A) express.static() B) express.parse() C) express.json() D) express.body() Answer: C

Q3: Which HTTP status code indicates that a resource was successfully created (e.g., after a POST request)? A) 200 B) 201 C) 204 D) 400 Answer: B

Q4: In a RESTful API, how would you construct the URL to retrieve a user with ID 45? A) GET /users/get?id=45 B) POST /users/45 C) GET /users/45 D) GET /user45 Answer: C

---

14. Interview Questions

  1. 1. What are the principles of REST?
*Answer:* REST is stateless (the server doesn't remember previous requests), it relies on standard HTTP methods (GET, POST, PUT, DELETE), and it uses structured, predictable URLs based on resources (nouns) rather than actions (verbs).
  1. 2. Why do we need express.json() middleware?
*Answer:* HTTP requests send data across the network as streams of raw text/binary. Express doesn't automatically know if the text is JSON, URL-encoded form data, or an image. express.json() specifically intercepts requests with a Content-Type: application/json header, parses the raw data into a JavaScript object, and attaches it to req.body.

---

15. FAQs

Q: Why use PUT instead of PATCH? A: Strictly speaking, PUT is used when you are replacing the *entire* object with a new one. PATCH is used when you are only updating one or two fields of an existing object. In reality, many developers just use PUT for all updates.

Q: Do I need a database to build an API? A: Eventually, yes. In-memory arrays (like we used here) vanish into thin air the moment you restart your Node server (Ctrl+C). A database persists the data permanently.

---

16. Summary

  • REST APIs send JSON data instead of HTML templates.
  • They utilize GET, POST, PUT, and DELETE HTTP methods.
  • The express.json() middleware parses incoming JSON into req.body.
  • Always return the correct HTTP Status Codes (200, 201, 400, 404).
  • Structure your URLs cleanly using resources (e.g., /api/todos/:id).

---

17. Next Chapter Recommendation

We learned how to handle JSON payloads. But what if a user is submitting a traditional HTML <form>? HTML forms don't send JSON! In Chapter 19: Handling Forms and User Input, we will learn how to parse x-www-form-urlencoded` data to handle standard website form submissions.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·