Menu
CoddyTech

Versionado

Parte de la sección API REST del Journey de Node.js de Coddy. Lección 22 de 25.

Cambiar la forma de una respuesta rompe todos los clientes que ya la utilizan. Una versión en la ruta permite que los clientes antiguos y los nuevos coexistan: /api/v1/orders hoy, /api/v2/orders cuando cambie la forma. Cuando una ruta cambia, la ruta antigua responde con una redirección a la nueva:

GET  /api/orders/5   →  308  Location: /api/v1/orders/5
POST /api/orders     →  308  Location: /api/v1/orders

Los códigos 301 y 302 permiten que los clientes repitan una solicitud POST como GET, lo que descarta el cuerpo. 308 indica «repite allí la misma solicitud», incluido el método y el cuerpo. Dentro de un middleware montado, req.url ha perdido la ruta de montaje, mientras que req.originalUrl la conserva completa:

// en app.use('/api/orders', ...) para GET /api/orders/5?full=1
req.url          // '/5?full=1'
req.originalUrl  // '/api/orders/5?full=1'
challenge icon

Desafío

Fácil

Tu API cambiará de formas que los clientes antiguos no esperarán, así que incluye un número de versión en sus rutas. La API de hoy pasa a ser la versión 1, en /api/v1/tasks, y las direcciones antiguas redirigen a los clientes allí.

  1. En server.js, monta el enrutador de tareas en /api/v1/tasks en lugar de /api/tasks.
  2. También en server.js, agrega un middleware montado en /api/tasks que solo redirija. Responde con el estado 308 a la misma dirección bajo /api/v1, conservando el resto de la ruta y la cadena de consulta. Dentro de un middleware montado, req.originalUrl contiene la dirección antigua completa. Por ejemplo:
    • GET /api/tasks/2 redirige a /api/v1/tasks/2.
    • POST /api/tasks redirige a /api/v1/tasks.
    • GET /api/tasks?done=false&sort=title redirige a /api/v1/tasks?done=false&sort=title.

    Usa 308, no 301: después de una respuesta 308, el cliente repite un POST como POST, incluido el cuerpo.

  3. En routes/tasks.js, la ruta POST establece Location con la nueva dirección; por ejemplo, /api/v1/tasks/4 para la tarea 4.

Pruébalo: GET /api/tasks/2 devuelve 308 y la pestaña Headers muestra Location: /api/v1/tasks/2. La barra de solicitudes muestra la redirección en sí; no la sigue. Envía { "title": "Order new chairs" } mediante POST a /api/tasks: obtienes 308 con Location: /api/v1/tasks y no se crea ninguna tarea. Envía el mismo cuerpo mediante POST a /api/v1/tasks: obtienes 201 con Location: /api/v1/tasks/4. GET /api/v1/tasks/2 devuelve la tarea 2.

Pruébalo tú mismo

import express from 'express';
import { sendError } from './errors.js';
import taskRoutes from './routes/tasks.js';

const BOARD_ORIGIN = 'https://board.example.com';

const app = express();

// Permite que las páginas de BOARD_ORIGIN llamen a esta API desde el navegador.
app.use((req, res, next) => {
    res.set('Access-Control-Allow-Origin', BOARD_ORIGIN);

    if (req.method === 'OPTIONS') {
        res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
        res.set('Access-Control-Allow-Headers', 'Content-Type');
        return res.status(204).end();
    }
    next();
});

app.use(express.json());

app.use(express.static('public'));

app.use('/api/tasks', taskRoutes);

// Los errores de las rutas llegan aquí. Un cuerpo que no contiene JSON válido es un
// error del cliente (400); cualquier otro error es nuestro (500).
app.use((err, req, res, next) => {
    if (err.type === 'entity.parse.failed') {
        return sendError(res, 400, 'invalid_json', 'The body is not valid JSON');
    }
    console.error(err);
    sendError(res, 500, 'server_error', 'Something went wrong');
});

app.listen(3000, () => {
    console.log('Task tracker API on port 3000');
});

Todas las lecciones de API REST

Practica por tu cuenta: Playground de Express