Conflictos
Parte de la sección API REST del Journey de Node.js de Coddy. Lección 13 de 25.
Una segunda tarea llamada «Enviar la factura» hace que la lista sea confusa, pero su cuerpo es válido. Eso es un 409 Conflicto: la solicitud entra en conflicto con lo que ya está almacenado. Un 400 significa que el propio cuerpo es incorrecto.
const taken = users.some((u) => u.name.toLowerCase() === name.toLowerCase());
if (taken) {
return sendError(res, 409, 'conflict', 'That username is taken');
}Compara en minúsculas, ya que la gente lee «Ada» y «ada» como el mismo nombre. Cuando una solicitud cambia el nombre de algo, omite el propio elemento en la búsqueda. De lo contrario, un usuario que guarda el formulario sin cambiar su nombre tendrá un conflicto consigo mismo.
Desafío
FácilDos tareas llamadas "Send the invoice" hacen que la lista resulte confusa. Rechaza un título que ya tenga otra tarea.
- En
routes/tasks.js, compara los títulos sin distinguir entre mayúsculas y minúsculas: "Book the meeting room" y "book the MEETING room" cuentan como el mismo título. - En POST, después de que se supere la validación del esquema: si alguna tarea ya tiene ese título, usa
sendErrorpara responder con el estado 409, y no añadas nada:{ "error": { "code": "conflict", "message": "A task with this title already exists" } } - Haz lo mismo en PATCH (solo cuando su cuerpo envíe un
title) y en PUT. En estas dos rutas, excluye de la búsqueda la tarea que se está modificando, para que una tarea pueda conservar su propio título o cambiar solo las mayúsculas. Un PATCH o PUT rechazado no cambia nada.
Pruébalo: POST { "title": "book the meeting room" } a /api/tasks: obtienes 409 con el código conflict, porque la tarea 2 se llama "Book the meeting room". PATCH /api/tasks/3 con { "title": "Write the Project Brief" }: 409, y GET /api/tasks/3 sigue mostrando "Send the invoice". PUT /api/tasks/2 con { "title": "Book the meeting room", "done": true }: 200, porque ese es el título de la propia tarea 2. POST { "title": "Order new chairs" }: 201.
Pruébalo tú mismo
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// Una tarea completa, tal como la envía PUT. .strict() rechaza los campos que no enumera.
const Task = z
.object({
title: z.string().trim().min(1).max(100),
done: z.boolean(),
})
.strict();
const NewTask = Task.partial({ done: true });
const TaskChanges = Task.partial();
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 sendError(res, 404, 'not_found', 'Task not found');
}
res.json(task);
});
router.post('/', (req, res) => {
const result = NewTask.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
const { title, done = false } = result.data;
const task = { id: nextId(), title, done, 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 sendError(res, 404, 'not_found', 'Task not found');
}
const result = TaskChanges.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
Object.assign(task, result.data);
res.json(task);
});
router.put('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return sendError(res, 404, 'not_found', 'Task not found');
}
const result = Task.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
const { id, createdAt } = tasks[index];
tasks[index] = { id, ...result.data, createdAt };
res.json(tasks[index]);
});
router.delete('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return sendError(res, 404, 'not_found', 'Task not found');
}
tasks.splice(index, 1);
res.status(204).end();
});
export default router;
Todas las lecciones de API REST
1Recursos y verbos
Recursos y URLCrear con POST4Navegadores, versiones y configuración
Qué es un origenPermitir otro origenCORSUna página que llama a la APIPractica por tu cuenta: Playground de Express