Menu
CoddyTech

버전 관리

Coddy Node.js 여정의 REST API 섹션에 포함된 레슨. 25개 중 22번째.

응답의 형태를 바꾸면 이미 그 응답을 사용하는 모든 클라이언트가 작동하지 않게 됩니다. 경로에 버전을 지정하면 이전 클라이언트와 새 클라이언트가 나란히 사용할 수 있습니다. 현재는 /api/v1/orders, 형태가 바뀌면 /api/v2/orders를 사용합니다. 경로가 바뀌면 이전 경로는 새 경로로 리디렉션을 응답합니다.

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

301과 302는 클라이언트가 POST를 GET으로 다시 요청하도록 허용하며, 이 경우 본문이 사라집니다. 308은 메서드와 본문을 포함해 "같은 요청을 해당 위치에서 다시 하라"고 나타냅니다. 마운트된 미들웨어 안에서 req.url에는 마운트 경로가 남아 있지 않지만, req.originalUrl에는 전체 경로가 유지됩니다.

// GET /api/orders/5?full=1 요청의 app.use('/api/orders', ...)에서
req.url          // '/5?full=1'
req.originalUrl  // '/api/orders/5?full=1'
challenge icon

챌린지

쉬움

기존 클라이언트가 예상하지 못하는 방식으로 API가 변경될 수 있으므로, 경로에 버전 번호를 넣으세요. 오늘의 API는 버전 1이 되어 /api/v1/tasks에서 제공되고, 이전 주소는 클라이언트를 이곳으로 보냅니다.

  1. server.js에서 작업 라우터를 /api/tasks 대신 /api/v1/tasks에 마운트하세요.
  2. 또한 server.js에서 /api/tasks에 마운트되어 리디렉션만 수행하는 미들웨어를 추가하세요. 이 미들웨어는 나머지 경로와 쿼리 문자열을 유지하면서 같은 주소의 /api/v1 버전으로 상태 코드 308을 응답합니다. 마운트된 미들웨어 안에서 req.originalUrl에는 이전 주소 전체가 들어 있습니다. 예를 들면 다음과 같습니다.
    • GET /api/tasks/2는 /api/v1/tasks/2로 리디렉션됩니다.
    • POST /api/tasks는 /api/v1/tasks로 리디렉션됩니다.
    • GET /api/tasks?done=false&sort=title은 /api/v1/tasks?done=false&sort=title로 리디렉션됩니다.

    301이 아니라 308을 사용하세요. 308을 받으면 클라이언트는 본문을 포함해 POST 요청을 POST 요청으로 다시 보냅니다.

  3. routes/tasks.js에서 POST 경로는 Location을 새 주소로 설정합니다. 예를 들어 작업 4의 경우 /api/v1/tasks/4로 설정합니다.

직접 해 보기: GET /api/tasks/2는 308을 반환하고, Headers 탭에는 Location: /api/v1/tasks/2가 표시됩니다. 요청 표시줄에는 리디렉션 자체가 표시되며, 리디렉션을 따라가지는 않습니다. /api/tasks에 { "title": "Order new chairs" }를 POST하면 Location: /api/v1/tasks와 함께 308을 받고, 작업은 생성되지 않습니다. 같은 본문을 /api/v1/tasks에 POST하면 Location: /api/v1/tasks/4와 함께 201을 받습니다. GET /api/v1/tasks/2는 작업 2를 반환합니다.

직접 해보기

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();

// BOARD_ORIGIN의 페이지에서 브라우저를 통해 이 API를 호출할 수 있게 합니다.
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);

// 라우트에서 발생한 오류는 여기서 처리됩니다. 유효한 JSON이 아닌 본문은
// 클라이언트의 잘못(400)이고, 그 밖의 오류는 서버의 잘못(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');
});

REST API의 모든 레슨

직접 연습해 보세요: Express 플레이그라운드