El manejador 404
Parte de la sección Conceptos básicos de Express del Journey de Node.js de Coddy. Lección 14 de 20.
Express responde a una ruta desconocida con su propia página, Cannot GET /x, que no encaja ni con tu sitio ni con tu API.
app.use((req, res) => {
res.status(404).send('No forecast here. Try /weather/oslo');
});Un middleware sin ruta se ejecuta en cada solicitud que llega hasta él. Coloca este después de todas las rutas y de express.static: las solicitudes que estos atendieron nunca llegan hasta allí, así que solo ve aquellas que nadie atendió. Como responde, no necesita next.
req.path es la ruta sin la cadena de consulta, así que req.path.startsWith('/api/') permite distinguir una llamada a la API de una página.
Desafío
Fácil- Una ruta dentro de
/api/a la que no responde ningún controlador devuelve404con{ "error": "Not found" }. - Cualquier otra ruta devuelve una página HTML
404que dicePage not found.
Envía GET /api/nope, abre /nope en la vista de página y después envía GET /api/recipes/1.
Pruébalo tú mismo
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.js';
const app = express();
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
app.use((req, res, next) => {
res.set('X-Request-Id', randomUUID());
next();
});
app.use(express.json());
app.use(express.static('public'));
const escapeHtml = (text) =>
text
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"');
app.get('/', (req, res) => {
const items = recipes.map((r) => `<li><a href="/recipes/${r.id}">${escapeHtml(r.title)}</a></li>`);
res.send(`<link rel="stylesheet" href="/style.css"><h1>Recipe Box</h1><ul>${items.join('')}</ul>`);
});
// type('text') envía texto sin formato, por lo que cada título aparece en su propia línea.
app.get('/recipes', (req, res) => {
const { tag } = req.query;
const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});
app.get('/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).send('No such recipe');
return;
}
res.send(recipe.title);
});
app.get('/api/recipes', (req, res) => {
res.json(recipes);
});
app.get('/api/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).json({ error: 'No such recipe' });
return;
}
res.json(recipe);
});
app.post('/api/recipes', (req, res) => {
const { title, tags } = req.body;
if (!title) {
res.status(400).json({ error: 'A recipe needs a title' });
return;
}
const recipe = { id: recipes.length + 1, title, tags: tags ?? [] };
recipes.push(recipe);
res.status(201).json(recipe);
});
app.get('/about', (req, res) => {
res.send('Recipe Box collects family recipes.');
});
app.get('/menu', (req, res) => {
res.redirect(301, '/recipes');
});
app.get('/menu/:id', (req, res) => {
res.redirect(301, `/recipes/${req.params.id}`);
});
app.listen(3000, () => {
console.log('Recipe Box is running on port 3000');
});