PLAINFINCH learn by building

Designing API routes

You will be able to design API routes named after things, and pick the right status code for each answer.

What makes an API easy to use?

An API is used by code someone else writes: a page’s JavaScript, a phone app, another server. That person reads your route list once and then guesses the rest. If the guesses work, the API is pleasant. If every route has its own naming style, they keep reading docs and making mistakes.

The style most web APIs follow is called REST. You don’t need the theory to use it. In practice it means three habits: name paths after things, use the HTTP method to say what to do with the thing, and answer with the status code that matches what happened.

Why name routes after things instead of actions?

The starting code has /api/addRepair and /api/deleteRepair/2. Every new action needs a new path, and there is no rule for guessing them: is it editRepair, updateRepair or changeRepair? Resource-style routes give the thing one address and let the method carry the verb.

Related things nest under their parent when the link matters. Notes on a repair could live at /api/repairs/2/notes, so the path itself says which repair they belong to. Keep nesting to one level; deeper paths get long and hard to remember, and the child usually has its own id anyway. Filters, sorting and searching stay in the query string, like /api/repairs?done=false, because they narrow a list instead of naming a new thing.

Use plural nouns for collections, /api/repairs, and add the id for one item, /api/repairs/2. Those two paths, combined with the five common methods, cover nearly everything a client wants to do with repairs.

  • GET /api/repairs lists repairs.
  • POST /api/repairs adds a repair to the collection.
  • GET /api/repairs/2 reads one repair.
  • PATCH /api/repairs/2 changes some fields of one repair. PUT replaces the whole thing.
  • DELETE /api/repairs/2 removes one repair.

Why must GET never change anything?

Send GET /api/deleteRepair/2, then GET /api/repairs. Repair 2 is gone. That is a real problem, because GET requests are sent freely by things you don’t control. Browsers load links ahead of time to make pages feel fast. Link checkers and search engine crawlers follow every link they find. Chat apps fetch links to build previews. Any of them could delete a repair just by looking at a page.

HTTP calls GET a safe method: it reads, and sending it never changes data on the server. Anything that creates, changes or deletes uses POST, PUT, PATCH or DELETE. A link can only ever send GET, so a delete button on a page needs a form or a fetch() call with the right method.

Which status code goes with each route?

Match the number to what happened. Clients often decide what to do from the status alone, before reading any body. A page might show a form’s errors on 400, a “not found” message on 404, and move on without a message on 204.

api.post('/repairs', (req, res) => {
  const repair = { id: nextId++, bike: req.body.bike, job: req.body.job, done: false };
  repairs.push(repair);
  res.status(201).set('Location', '/api/repairs/' + repair.id).json(repair);
});
  • 200 OK for a successful read or update that returns data.
  • 201 Created when a POST made something new. Add a Location header with its address, such as /api/repairs/3, and send the new item in the body.
  • 204 No Content when a request succeeded and there is nothing to send back, typical for DELETE. A 204 never has a body.
  • 400 Bad Request when the body fails validation.
  • 404 Not Found when the id doesn’t exist, for reads, updates and deletes alike.
FigureBike shop repair routes on a router mounted at /api. Action routes POST /addRepair and GET /deleteRepair/:id become POST /api/repairs answering 201 with Location /api/repairs/3, and DELETE /api/repairs/:id answering 204 or 404.Bike shop repair routes on a router mounted at /api. Action routes POST /addRepair and GET /deleteRepair/:id become POST /api/repairs answering 201 with Location /api/repairs/3, and DELETE /api/repairs/:id answering 204 or 404.
The address names the thing, /api/repairs, and the method says what to do with it. Creating answers 201 with a Location header pointing at the new repair; deleting answers 204, or 404 when that id is gone.

What does express.Router() add?

A router is a small app of routes with no server of its own. You add routes to it the same way, with api.get() and api.post(), and then mount it with app.use('/api', api). Every path inside gets the /api prefix, so api.get('/repairs') answers GET /api/repairs.

That keeps paths short inside the router and lets you move the whole API with one line, for example to /api/v2. In bigger projects each resource gets its own router in its own file, repairs.js, bookings.js, and server.js mounts them. The Requests view shows the full route that handled each request, prefix included.

Routers can also carry their own middleware. api.use(express.json()) parses bodies only for API routes, and later lessons add login checks the same way. A frequent slip is repeating the prefix: api.get('/api/repairs') mounted at /api answers at /api/api/repairs. If every route under a router gives 404, check that first.

const api = express.Router();
api.get('/repairs', listRepairs);
api.delete('/repairs/:id', deleteRepair);

app.use('/api', api); // GET /api/repairs, DELETE /api/repairs/2

How should a DELETE route behave?

Find the item by id. If it doesn’t exist, answer 404 and stop. Otherwise remove it and answer 204 with res.sendStatus(204) or res.status(204).end(). Sending a body with a 204 does nothing useful: Express drops it, because the status itself says there is no content.

Some APIs answer a delete of something already gone with 204 anyway, on the grounds that the result is the same: it isn’t there. Either choice is defensible. Answering 404 helps a client notice a wrong id, and it is what this course uses.

Think about what else points at the deleted item. If bookings refer to repair 2, deleting the repair leaves them pointing at nothing. Small APIs often refuse the delete with 409 Conflict while something still depends on the item, or mark it as cancelled instead of removing it. Which one fits depends on whether anyone will ever need the old record, for example to answer a customer’s question about last month’s brakes.

How do you check an API by hand?

The request bar sends any method, so you can test every route without a page. Pick DELETE, type /api/repairs/1, press Send, and read the status in the Requests view. Then GET /api/repairs/1 to confirm it is gone. Open “Headers and body” on a POST to see the Location header.

Testing in this order catches most mistakes: list, read one, create, read the new one, update, delete, read it again and expect 404. Then try each route with an id that doesn’t exist. When AI tools write an API for you, run the same list. Look especially for 200 where 201 belongs, delete or update routes on GET, missing 404 checks, and routes that mix naming styles.

Keep the answers consistent too. If one route sends errors as { error: '...' }, every route should, and a list should always be an array, even when it is empty. Clients write their code once against the first answer they see, and every inconsistency becomes a special case somewhere else.

Field notes

  • Name routes after the thing, like /api/repairs, and let the method say the action.
  • A GET request should only read data and never change or delete anything.
  • Answer 201 for something created, 204 for a delete with no body, and 404 when the item is missing.
  • A router groups related routes so you can mount them under one path.

Easy to get wrong

A GET that deletes can be triggered by any link, prefetch or crawler. Changes belong on POST, PUT, PATCH or DELETE.

The mount path is added to every route on the router, so the first version answers at /api/api/repairs and /api/repairs gives 404.

A new item should come back with 201 Created and its address. Plain 200 hides that something was made.

Questions people ask

Should I use PUT or PATCH to update?

Use PATCH when the client sends only the fields to change, such as { "done": true }. Use PUT when the client sends the whole item and it replaces what was there. Most small APIs only need PATCH.

Why put the API under /api?

It keeps API routes apart from pages, so /repairs can be an HTML page for people and /api/repairs the JSON for code. It also makes it easy to apply things like CORS or rate limits to the API alone.

Is REST the only way to design an API?

No. Some APIs send every request to one address and describe the operation in the body, and some use other protocols entirely. For a site’s own data, resource routes with plain HTTP methods are the most common and the easiest to read and test.

Does Express have res.location()?

Yes, real Express has res.location(url), which sets the same header. This course uses res.set('Location', ...) because it works everywhere and makes the header name obvious.

Keep going