Replacing with PUT
Part of the REST APIs section of Coddy's Node.js journey. Lesson 5 of 25.
An edit form sends back the whole task, every field at once. PUT means "replace the resource with this", so the body must be complete.
PATCH /api/invoices/7 { "paid": true } → paid changes
PUT /api/invoices/7 { "amount": 90, "paid": true } → invoice 7 replaced
PUT /api/invoices/7 { "paid": true } → 400: amount missingA PUT with a field missing answers 400: replacing with half an invoice would lose the amount. Fields the server owns, like the id and the creation time, come from the old version, never from the body:
const { id, issuedAt } = invoices[index];
invoices[index] = { id, amount, paid, issuedAt };Challenge
EasyAn edit form sends the whole task back at once. Add a PUT route that replaces a task with what the client sends.
- In
routes/tasks.js, addPUT /api/tasks/:id. Its body must hold both fields, for example{ "title": "Book the big room", "done": true }. - Replace the task's
titleanddonewith the body's values. Keep itsidandcreatedAtfrom the stored task; never take them from the body. Respond with status 200 and the whole task:{ "id": 2, "title": "Book the big room", "done": true, "createdAt": "2026-03-03T10:30:00.000Z" } - If the body is missing
titleordone, respond with status 400 and{ "error": "PUT needs every field: title and done" }, and change nothing. A field is missing only when it is not in the body at all:"done": falseis a real value. - If no task has that id, respond with status 404 and
{ "error": "Task not found" }, like the other routes.
Try it: PUT /api/tasks/2 with { "title": "Book the big room", "done": true } returns 200 and the task above. Then PUT /api/tasks/2 with only { "done": true } returns 400, and GET /api/tasks/2 still shows "Book the big room". PUT /api/tasks/99 with both fields returns 404.
Try it yourself
import { Router } from 'express';
import { nextId, tasks } from '../store.js';
const router = Router();
router.get('/', (req, res) => {
res.json(tasks);
});
router.get('/:id', (req, res) => {
const task = tasks.find((t) => t.id === Number(req.params.id));
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
res.json(task);
});
router.post('/', (req, res) => {
const { title } = req.body ?? {};
if (!title) {
return res.status(400).json({ error: 'title is required' });
}
const task = { id: nextId(), title, done: false, createdAt: new Date().toISOString() };
tasks.push(task);
res.status(201).location(`/api/tasks/${task.id}`).json(task);
});
router.patch('/:id', (req, res) => {
const task = tasks.find((t) => t.id === Number(req.params.id));
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
const { title, done } = req.body ?? {};
if (title !== undefined) task.title = title;
if (done !== undefined) task.done = done;
res.json(task);
});
router.delete('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Task not found' });
}
tasks.splice(index, 1);
res.status(204).end();
});
export default router;
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in REST APIs
1Resources and Verbs
Resources and URLsCreating with POST4Browsers, Versions and Config
What Is an OriginAllowing Another OriginCORSA Page That Calls the APIPractice on your own: Express playground