# Building REST APIs

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: APIs and Errors
Source: https://www.devtutorials.ai/learn/expressjs/api-errors/rest-apis

> REST basics, idempotency, API routers and versioning, collection routes with filtering, search, sorting, pagination and field selection, create, read, update and delete, PUT versus PATCH, status codes, consistent JSON and route structure.

## REST APIs Introduction

**API** = a set of URLs that programs call to read and change data.

- **REST** = a common style for web APIs, built on plain HTTP.
- **Resource** = one kind of thing, such as a menu item or an order.
- **Collection** = the list of all of them: `/api/menu`.
- **Single resource** = one of them: `/api/menu/3`.
- HTTP methods say what to do. JSON carries the data.

**Simple lesson:** A REST API is nouns in the URL and verbs in the method.

### A Real-World Example

The canteen app on students' phones needs data, not web pages.

1. The app asks `GET /api/menu`. It gets a JSON list.
2. A student orders: `POST /api/orders` with the items.
3. The kitchen marks it ready: `PATCH /api/orders/41` with `{ "status": "ready" }`.
4. A wrong order is cancelled: `DELETE /api/orders/41`.

Every action is a method plus a URL. The phone app, a website and a kitchen screen can all use the same API.

In this lesson, you will learn:

1. REST basics: resources, statelessness and idempotency.
2. How to organise API routers and versions.
3. How to list, filter, sort and page collections.
4. How to create, read, update and delete resources.
5. Which status codes to send and when.
6. How to structure routes, validation and errors.
7. How to document your API with OpenAPI.

## REST Fundamentals

- **Resource endpoints:** `GET /api/menu`, `GET /api/menu/3`.
- **Stateless:** each request carries everything needed. The server does not remember the last request.
- **Methods map to actions:** GET reads, POST creates, PUT replaces, PATCH changes, DELETE removes.
- **Idempotent** = repeating the request has the same effect as doing it once.
- GET, PUT and DELETE are idempotent. POST is not, and PATCH may not be.

<figure className="xfig ngc-fig">
<svg viewBox="0 0 560 200" role="img" aria-labelledby="xfig-crud" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
  <title id="xfig-crud">A table mapping HTTP methods on /api/menu and /api/menu/:id to create, read, update and delete</title>
  <g style={{ fontFamily: "var(--mono)", fontSize: 12 }}>
    <rect x="10" y="10" width="540" height="180" rx="10" style={{ fill: "var(--panel)", stroke: "var(--line)", strokeWidth: 1.5 }} />
    <text x="30" y="36" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontWeight: 700 }}>Method</text>
    <text x="120" y="36" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontWeight: 700 }}>URL</text>
    <text x="300" y="36" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontWeight: 700 }}>Action</text>
    <text x="440" y="36" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontWeight: 700 }}>Success</text>
    <text x="30" y="64" style={{ fill: "var(--ngc-blue)", fontWeight: 700 }}>GET</text><text x="120" y="64" style={{ fill: "var(--ink)" }}>/api/menu</text><text x="300" y="64" style={{ fill: "var(--ink)", fontFamily: "var(--sans)" }}>List all</text><text x="440" y="64" style={{ fill: "var(--ngc-green)" }}>200</text>
    <text x="30" y="88" style={{ fill: "var(--ngc-green)", fontWeight: 700 }}>POST</text><text x="120" y="88" style={{ fill: "var(--ink)" }}>/api/menu</text><text x="300" y="88" style={{ fill: "var(--ink)", fontFamily: "var(--sans)" }}>Create one</text><text x="440" y="88" style={{ fill: "var(--ngc-green)" }}>201 + Location</text>
    <text x="30" y="112" style={{ fill: "var(--ngc-blue)", fontWeight: 700 }}>GET</text><text x="120" y="112" style={{ fill: "var(--ink)" }}>/api/menu/:id</text><text x="300" y="112" style={{ fill: "var(--ink)", fontFamily: "var(--sans)" }}>Read one</text><text x="440" y="112" style={{ fill: "var(--ngc-green)" }}>200</text>
    <text x="30" y="136" style={{ fill: "var(--ngc-purple)", fontWeight: 700 }}>PUT</text><text x="120" y="136" style={{ fill: "var(--ink)" }}>/api/menu/:id</text><text x="300" y="136" style={{ fill: "var(--ink)", fontFamily: "var(--sans)" }}>Replace one</text><text x="440" y="136" style={{ fill: "var(--ngc-green)" }}>200</text>
    <text x="30" y="160" style={{ fill: "var(--ngc-orange)", fontWeight: 700 }}>PATCH</text><text x="120" y="160" style={{ fill: "var(--ink)" }}>/api/menu/:id</text><text x="300" y="160" style={{ fill: "var(--ink)", fontFamily: "var(--sans)" }}>Change part</text><text x="440" y="160" style={{ fill: "var(--ngc-green)" }}>200</text>
    <text x="30" y="184" style={{ fill: "var(--bad)", fontWeight: 700 }}>DELETE</text><text x="120" y="184" style={{ fill: "var(--ink)" }}>/api/menu/:id</text><text x="300" y="184" style={{ fill: "var(--ink)", fontFamily: "var(--sans)" }}>Remove one</text><text x="440" y="184" style={{ fill: "var(--ngc-green)" }}>204</text>
  </g>
</svg>
<figcaption>Two URLs and five methods cover every basic action on a resource.</figcaption>
</figure>

Idempotency matters when networks fail. If a phone loses signal and resends a DELETE, nothing extra happens. If it resends a POST, a second order may be created, so apps often add an idempotency key header for important POSTs.

**Simple lesson:** Collections and single resources, with methods for actions.

## Organising API Routes

- **One router per resource:** `routes/menu.js`, `routes/orders.js`.
- **Base path:** mount APIs under `/api`, so they never clash with pages.
- **Versioning:** `/api/v1/...`. A breaking change goes to `/api/v2` while old apps keep working.
- **Nested resources:** items inside an order: `/api/v1/orders/:orderId/items`.
- Use `mergeParams: true` for nested routers. See **Routing and Routers**.

- Every answer has the same shape: `{ data }` for success, `{ error }` for problems.

**Simple lesson:** `/api/v1/<resource>` with one router per resource.

## Collection Routes

A good list endpoint lets clients shape the list with query options.

- **Filter:** `?veg=true`.
- **Search:** `?q=dosa`.
- **Sort:** `?sort=price` or `?sort=-price` for high to low.
- **Paginate:** `?page=2&limit=10`.
- **Select fields:** `?fields=name,price`.
- Send page details too, so clients know how many pages exist.

- An empty result is still **200** with `data: []`. "Nothing matched" is not an error.
- `limit` is capped at 50, so nobody can ask for a million rows.

**Simple lesson:** Lists answer 200 with data and page details, even when empty.

## Creating Resources

- Read the JSON from `req.body`, and validate it. See **Input Processing and Validation**.
- Create a new **identifier**, usually from the database or `crypto.randomUUID()`.
- Answer **201 Created**.
- Send the new resource back, so the client sees its ID.
- Set the **Location** header to the new resource's URL.

## Reading, Updating and Deleting

- **Read one:** 200 with the resource, or 404 when it does not exist.
- **PUT** = replace the whole resource. Missing fields are a mistake.
- **PATCH** = change only the fields sent. Others stay as they are.
- **409 Conflict** = the change clashes with current data, such as a duplicate name.
- **Hard delete** = remove it for good. **Soft delete** = mark it with `deletedAt` and hide it.
- **DELETE** answers **204 No Content** with an empty body.

- Look at **Headers** on the first POST: `Location: /api/v1/menu/3`.
- Adding Idli again gives **409**.
- PUT without a price gives **422**. PATCH with only a price works.
- After DELETE, Idli is gone from the list, but still in the array with a `deletedAt` date.

Easy way to remember: **PUT = the whole plate. PATCH = just add more chutney.**

**Simple lesson:** 201 + Location for create, 200 for read and update, 204 for delete.

## JSON Responses

- Use `res.json()`, or `res.status(code).json()` when the code is not 200.
- Return **objects** at the top level, such as `{ data: [...] }`, not bare arrays. You can add `meta` later.
- Empty results: `{ data: [] }` for lists, 204 for "done, nothing to say".
- Keep one shape everywhere: `{ data, meta }` for success, `{ error: { code, message } }` for failure.
- **Never send internal fields**, such as password hashes, `deletedAt` or staff notes.

- The unsafe route leaks the password hash, the fees and a private note.
- `toPublic` makes the list of public fields clear, and new database columns stay private by default.

**Simple lesson:** One response shape for the whole API, with nothing private inside.

## REST Status Codes

### Success

- **200 OK:** read or update worked.
- **201 Created:** a new resource exists. Add `Location`.
- **202 Accepted:** queued for later, such as sending a report by email.
- **204 No Content:** worked, and there is nothing to send back.

### Errors

- **400 Bad Request:** the request cannot be read, such as broken JSON.
- **401 Unauthorized:** not logged in. **403 Forbidden:** logged in, not allowed.
- **404 Not Found:** no such resource.
- **409 Conflict:** clashes with current data.
- **422 Unprocessable Content:** readable, but breaks the rules.
- **429 Too Many Requests:** slow down. See **Express Security**.
- **500 Internal Server Error:** your code failed. Send a safe message only.

A 500 answer must never contain error stacks, SQL or file paths. Log the details on the server. See **Error Handling**.

**Simple lesson:** The status code is part of your API. Pick it as carefully as the data.

## Route Implementation Structure

A finished API route is a short chain of small parts:

1. **Router definition:** the method and path.
2. **Authentication middleware:** who is calling?
3. **Access-control middleware:** may they do this?
4. **Validation middleware:** is the input correct?
5. **Route handler:** do the work and send the answer.
6. **Central error middleware:** turn any thrown error into a safe JSON answer.

- Watch the codes: 401, 201, 422, 403, 204.
- Each concern lives in its own file, and the route reads like a sentence.

**Simple lesson:** Authenticate, authorise, validate, handle, and catch errors in one place.

## Documenting the API with OpenAPI

Other developers cannot read your code. They read your API docs.

- **OpenAPI** = a standard JSON or YAML file that lists every route, its input and its answers.
- Tools turn it into a web page where people can **try** each route. The most common is **Swagger UI**.
- It can also make client code, mock servers and tests.
- Keep it next to your routes and update it in the same commit.
- **Code-first:** write the routes, then the spec. **Design-first:** write the spec, then build to match it.

```js
import swaggerUi from "swagger-ui-express";

app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(openapi)); // a "try it" web page
```

- `$ref` reuses one schema in many places, like a variable.
- Open `/api/docs` in the browser on your computer to see and try every route.

**Simple lesson:** An OpenAPI file is the user manual of your API. Serve it, and keep it up to date.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `POST /api/createItem` | Verbs belong in URLs | Nouns in URLs, verbs in methods | A messy, guessable-only API |
| 200 for a new resource | Any 2xx is fine | 201 with `Location` | Clients cannot find the new item |
| 404 for an empty list | No results is "not found" | 200 with `data: []` | Apps show an error for no matches |
| PUT that keeps missing fields | PUT and PATCH are the same | PUT replaces, PATCH changes part | Data silently changes |
| Sending the whole database row | Extra fields are harmless | Pick the fields to send | Password hashes and notes leak |
| No `limit` cap | Clients ask for sensible sizes | Cap page size | One request downloads everything |
| A different error shape per route | Each route can choose | One shape everywhere | Clients need special code per route |

</div>

## Check Yourself

## Exercises

## What to Remember

- REST: nouns in URLs, verbs in methods, JSON in bodies.
- Mount APIs under `/api/v1`, one router per resource.
- Lists support filter, search, sort, page and fields, and answer 200 even when empty.
- Create: 201 + Location. Read and update: 200. Delete: 204.
- PUT replaces, PATCH changes part. 409 for conflicts, 422 for rule breaks.
- One response shape, no private fields, and errors handled in one place.
- Describe every route in an OpenAPI file, and serve it.

Next: **Error Handling**. You will catch every kind of error and answer safely.
