REST API Development with Express
# 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);
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 } ];
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); });
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 }); });
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" }); });
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. What are the principles of REST?
-
2.
Why do we need express.json()
middleware?
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.