Suggest an editImprove this articleRefine the answer for “How to build a REST API with Express.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**REST API with Express.js** maps HTTP methods to handler functions for CRUD operations on resources. ```js app.use(express.json()); app.get('/users', (req, res) => res.json(users)); app.post('/users', (req, res) => { const user = { id: nextId++, ...req.body }; users.push(user); res.status(201).json(user); }); ``` **Key point:** `express.json()` must be registered before any route that reads `req.body`, or the body will be `undefined`.Shown above the full answer for quick recall.Answer (EN)Image**REST API with Express.js** is a server that maps HTTP methods and URL paths to handler functions, each one reading, creating, updating, or deleting a resource. ## Theory ### TL;DR - Call `app.use(express.json())` before any route that reads `req.body`, or `req.body` will be `undefined` - GET reads, POST creates, PUT replaces the whole object, PATCH updates specific fields, DELETE removes - Status codes are part of the contract: 201 for created, 204 for deleted (no body), 404 for not found, 400 for bad input - Route parameters live in `req.params`, query strings in `req.query`, body data in `req.body` - URL resource names should be plural nouns: `/users`, not `/user` or `/getUsers` ### Minimal working example ```js const express = require('express'); const app = express(); app.use(express.json()); // without this, req.body is undefined app.get('/users', (req, res) => { res.json([{ id: 1, name: 'Alice' }]); // returns JSON array }); app.post('/users', (req, res) => { const { name } = req.body; // reads parsed JSON body res.status(201).json({ id: 2, name }); // 201 = Created }); app.listen(3000); ``` `express.json()` parses the incoming request body as JSON and puts the result on `req.body`. Without that one line, POST and PUT handlers get nothing. ### HTTP methods and CRUD | HTTP Method | CRUD | When to use | |---|---|---| | GET | Read | Retrieve a resource or list | | POST | Create | Create a new resource | | PUT | Update (full) | Replace the entire resource | | PATCH | Update (partial) | Change one or a few fields | | DELETE | Delete | Remove a resource | PUT replaces the whole object. If you PUT `{ name: 'Alice' }` to a user that also has an email field, the email disappears. PATCH only touches what you send. Most real-world APIs default to PATCH for updates because clients rarely want to resend unchanged data. ### Reading request data ```js // GET /users/42?format=json app.get('/users/:id', (req, res) => { req.params.id // '42' — always a string req.query.format // 'json' req.headers['authorization'] // Bearer token req.method // 'GET' req.path // '/users/42' }); // POST /users with JSON body app.post('/users', (req, res) => { req.body.name // from parsed JSON req.body.email }); ``` `req.params.id` is always a string. Comparing it to a numeric ID with `===` never matches. Convert with `Number(req.params.id)` before the lookup. ### Sending responses ```js res.json({ data }) // sends JSON, sets Content-Type automatically res.status(201).json(data) // chain status then body res.status(204).send() // no body — for successful DELETE res.status(404).json({ error: 'Not found' }) res.redirect('/new-path') ``` Express defaults every response to 200. A client reading a 200 from a POST has no signal that the resource was actually created. Set the code explicitly. ### Design rules 1. Nouns in URLs, not verbs: `/users`, not `/getUsers` or `/deleteUser` 2. Plural resource names: `/users`, not `/user` 3. Version from day one: `/api/v1/users` 4. Consistent JSON shape: same structure for success and error in every response 5. Let HTTP methods carry the action, not URL paths Versioning matters more than it seems. Once you ship `/api/users` to a client, you cannot change the response shape without breaking them. Adding `/api/v2/users` lets you iterate freely while v1 stays stable. ### Common mistakes **Missing `express.json()`** ```js // Wrong — req.body is undefined, no error thrown app.post('/users', (req, res) => { const user = { ...req.body }; // {} — empty spread, data is lost users.push(user); res.status(201).json(user); }); // Correct app.use(express.json()); // add this before route definitions ``` **Wrong status codes** ```js // Wrong — 200 on create, 200 on delete app.post('/users', (req, res) => res.json(user)); app.delete('/users/:id', (req, res) => res.json({ ok: true })); // Correct app.post('/users', (req, res) => res.status(201).json(user)); app.delete('/users/:id', (req, res) => res.status(204).send()); ``` **Forgetting to convert `req.params.id`** ```js // Wrong — string '42' never strictly equals number 42 const user = users.find(u => u.id === req.params.id); // Correct const user = users.find(u => u.id === Number(req.params.id)); ``` **Verbs in the URL** ```js // Wrong app.post('/createUser', ...); app.get('/deleteUser/:id', ...); // Correct app.post('/users', ...); app.delete('/users/:id', ...); ``` ### Where you see this in production - Express handles the HTTP layer in many Node.js backends, often alongside a database ORM - Routes in real projects live in separate files using `express.Router()` - `express.urlencoded()` handles HTML form submissions; `express.json()` handles API clients - Validation happens before the database call: check required fields in the handler, or use a library like `zod` or `joi` ### Follow-up questions **Q:** What is the difference between PUT and PATCH? **A:** PUT replaces the entire resource. Fields you omit get wiped. PATCH only updates the fields you include. Most teams use PATCH for updates to avoid accidentally deleting data. **Q:** Why return 204 instead of 200 on DELETE? **A:** 204 means success with no body. The resource no longer exists, so there is nothing to return. 200 with an empty body also works, but 204 is the accepted convention. **Q:** What happens when two routes match the same path? **A:** Express runs the first matching route and stops. The second one never fires unless the first handler calls `next()`. This matters when you stack middleware above route handlers. **Q:** How do you split routes into separate files as the app grows? **A:** Use `express.Router()`. Define routes on a router instance, export it, then `app.use('/users', usersRouter)` in the main file. The router handles `'/'` and `'/:id'`; the prefix comes from `app.use`. **Q:** How would you protect a route so only authenticated users can access it? **A:** Write a middleware function that checks the `Authorization` header, validates the token, and either calls `next()` or sends `res.status(401).json({ error: 'Unauthorized' })`. Add it before the route handler: `app.get('/users', authMiddleware, handler)`. ## Examples ### Complete CRUD for a users resource ```js const express = require('express'); const app = express(); app.use(express.json()); let users = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' }, ]; let nextId = 3; // GET /users — list all app.get('/users', (req, res) => { res.json(users); }); // GET /users/:id — get one app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === Number(req.params.id)); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user); }); // POST /users — create app.post('/users', (req, res) => { const { name, email } = req.body; if (!name || !email) { return res.status(400).json({ error: 'name and email are required' }); } const user = { id: nextId++, name, email }; users.push(user); res.status(201).json(user); }); // PUT /users/:id — replace app.put('/users/:id', (req, res) => { const index = users.findIndex(u => u.id === Number(req.params.id)); if (index === -1) return res.status(404).json({ error: 'User not found' }); users[index] = { id: Number(req.params.id), ...req.body }; res.json(users[index]); }); // PATCH /users/:id — partial update app.patch('/users/:id', (req, res) => { const user = users.find(u => u.id === Number(req.params.id)); if (!user) return res.status(404).json({ error: 'User not found' }); Object.assign(user, req.body); res.json(user); }); // DELETE /users/:id — remove app.delete('/users/:id', (req, res) => { const index = users.findIndex(u => u.id === Number(req.params.id)); if (index === -1) return res.status(404).json({ error: 'User not found' }); users.splice(index, 1); res.status(204).send(); }); app.listen(3000, () => console.log('API running on port 3000')); ``` Each handler returns early on the not-found case. That keeps the happy path flat and avoids nested `if` blocks. Worth making this the default pattern from the first route you write. ### Consistent response shape ```js // Success res.json({ success: true, data: user }); // Validation error res.status(400).json({ success: false, error: 'name and email are required' }); // Paginated list res.json({ success: true, data: users, pagination: { page: 1, limit: 10, total: 100 } }); ``` Clients check `success`, then read `data` or `error`. One consistent shape means one place in client code handles every response type. ### Splitting routes with express.Router ```js // routes/users.js const router = require('express').Router(); router.get('/', (req, res) => res.json(users)); router.get('/:id', (req, res) => { /* get one */ }); router.post('/', (req, res) => { /* create */ }); module.exports = router; // app.js const usersRouter = require('./routes/users'); app.use('/users', usersRouter); // GET /users -> router.get('/') // GET /users/1 -> router.get('/:id') ``` The router file does not know its own prefix. `app.use('/users', usersRouter)` attaches the prefix at mount time. So inside the router you write `/` and `/:id`, not `/users` and `/users/:id`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.