Designing a JSON API
An API is a promise to whoever calls it: send me this, and I will answer with that. A good one is predictable, so a frontend developer can guess the next route without reading your code. This lesson builds a small tasks API and covers the decisions that come up in almost every project: route names, response shapes, input checks, status codes, and what happens when the same request arrives twice.
Resources and routes
Design around the things your app stores (resources) and let the HTTP method say what you are doing to them. For tasks:
GET /taskslists tasksPOST /taskscreates a taskGET /tasks/:idreads one taskPUT /tasks/:idreplaces a task (PATCHchanges only some fields)DELETE /tasks/:iddeletes a task
Here :id is a placeholder, so GET /tasks/7 reads task 7. Avoid routes like /getAllTasks or /createTask: the verb already lives in the method, and plural nouns keep every resource consistent.
JSON in, JSON out
Requests and responses carry JSON bodies. Keep shapes consistent: a single task is always the same object, a list is always an array of those objects, and errors always look alike.
{ "id": 7, "title": "Buy milk", "done": false }
{ "error": "title is required" }
Consistency matters because the client parses whatever you send. If your framework answers some errors with an HTML error page, a client that calls res.json() crashes with "Unexpected token <", which hides the real problem.
Validate on the server
Your frontend form may check that the title is filled in, but anyone can call your API directly with curl or a script and send anything. So the server checks every input, every time, before using it:
app.post('/tasks', (req, res) => {
const { title } = req.body;
if (typeof title !== 'string' || title.trim() === '') {
return res.status(400).json({ error: 'title is required' });
}
const task = { id: nextId++, title: title.trim(), done: false };
tasks.push(task);
res.status(201).json(task);
});
Frontend validation is for the user's convenience. Server validation is what actually protects your data.
The right status code
Status codes let the client react without reading your error text:
200for a successful read or update, with the data in the body.201when something was created. Send the new object back, including itsid, so the client can link to it or update it next.204for a success with nothing to return, common for DELETE.400when the input is invalid, with a message saying which field.404when/tasks/999does not exist. Not a200withnull, and not a500.500only for real bugs on your side. If bad input causes a 500, you are missing a validation check.
Idempotency: the double-click problem
A request is idempotent if sending it twice leaves the server in the same state as sending it once. GET, PUT and DELETE are idempotent by design: deleting task 7 twice still leaves task 7 deleted (the second call may answer 404, but nothing else changes), and putting the same full task twice gives the same task.
POST is not. Two identical POST /tasks calls make two tasks. That is harmless for a to-do list and a disaster for POST /payments: a user double-clicks "Pay", or a slow network makes the app retry, and the card is charged twice.
Disabling the button after the first click helps, but it does not cover retries or two open tabs. The reliable fix is on the server: the client creates a unique idempotency key once per payment attempt, and the server refuses to process the same key twice.
app.post('/payments', async (req, res) => {
const key = req.get('Idempotency-Key');
const earlier = await findPaymentByKey(key);
if (earlier) return res.status(200).json(earlier);
const payment = await chargeCard(req.body, key);
res.status(201).json(payment);
});
A common mistake
Treating PUT like PATCH. By the usual meaning, PUT replaces the whole resource, so a client that sends only { "done": true } to a replace-style PUT can wipe out the title. If you want partial updates, offer PATCH and document it. Try it: sketch the five routes for a notes resource, write the JSON for one note, and decide the status code for each success and each failure.
Resources
Curated resources for this node are on the way. Use what you already know how to search for, and check back soon.