07. 라우트 모듈화

프로젝트를 진행하다보면, 여러종류의 라우트를 만들게 될 것입니다. 하지만, 각 라우트를 index.js 파일 하나에서 모두 작성을 하게 된다면, 코드가 너무 길어지게 되며, 유지보수도 하기 힘들어집니다. 이번에는, 라우터를 여러 파일에 분리시켜서 작성하고, 이를 불러와서 적용하는 방법을 알아보도록 하겠습니다.

우선, 라우트를 저장 할 디렉토리 부터 만들도록 하겠습니다. src/api/ 디렉토리를 생성하고, 이 내부에 index.js 파일을 생성하세요.

src/api/index.js

const Router = require('koa-router');

const api = new Router();

api.get('/test', (ctx) => {
  ctx.body = 'test 성공';
});

// 라우터를 내보냅니다.
module.exports = api;

그 다음에는, 이 api 라우트를 src/index.js 에서 불러와서 기존의 라우터에 /api 라는 경로로 적용하세요. 기존에 만들었었던 라우트들은 제거하겠습니다.

src/index.js

const Koa = require('koa');
const Router = require('koa-router');

const api = require('./api');

const app = new Koa();
const router = new Router();

// 라우터 설정
router.use('/api', api.routes()); // api 라우트 적용

// app 인스턴스에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());

app.listen(4000, () => {
  console.log('listening to port 4000');
});

우리가 만든 api 라우터가, 서버의 메인 라우터의 /api 경로로 설정되었습니다. 따라서, /api/test 경로로 요청을 하면 우리가 아까 준비했던 ‘test 성공’ 문자열이 나타날 것입니다.

코드를 저장하고 http://localhost:4000/api/test 를 띄워보세요.

/api/test 라우트가 제대로 작동했나요?

posts 라우트 만들기

이번엔, api 라우트 내부에 posts 라우트를 만들어보겠습니다. src/api/posts 디렉토리를 만들고, 그 내부에 index.js 파일을 생성하세요.

그 다음에, 다음 코드를 입력하세요:

src/api/posts/index.js

const Router = require('koa-router');

const posts = new Router();

const printInfo = (ctx) => {
  ctx.body = {
    method: ctx.method,
    path: ctx.path,
    params: ctx.params,
  };
};

posts.get('/', printInfo);
posts.post('/', printInfo);
posts.get('/:id', printInfo);
posts.delete('/:id', printInfo);
posts.put('/:id', printInfo);
posts.patch('/:id', printInfo);

module.exports = posts;

이번에는, posts 라우트에 여러종류의 라우트를 설정 한 뒤, 모두 printInfo 라는 함수를 호출하도록 설정하였습니다. 이번엔, 문자열이 아닌 JSON 객체를 반환하도록 설정하였고, 이 객체에는 현재 요청의 메소드와, 경로와, 파라미터를 담았습니다.

코드를 완성한 다음엔, api 라우트에 이 posts 라우트를 연결시키세요. 연결시키는 방법은 서버의 메인 파일에서 api 라우트를 적용 할 때와 비슷합니다.

src/api/index.js

const Router = require('koa-router');
const posts = require('./posts');

const api = new Router();

api.use('/posts', posts.routes());

// 라우터를 내보냅니다.
module.exports = api;

기존의 test 라우트는 지우고, posts 라우트를 불러와서 설정해주었습니다. 우선, GET /api/posts 라우트부터 테스트를 해보겠습니다. http://localhost:4000/api/posts 를 브라우저로 띄워보세요

잘 나타났나요? 이제 나머지 API 들도 테스팅을 해봐야 하는데요, GET API는 브라우저 상에서 주소를 입력하여 테스팅 할 수 있는 방면, POST, DELETE, PUT, PATCH 메소드를 사용하는 API 들은 자바스크립트로 호출을 해야합니다.

편의를 위해서, REST API 요청 테스팅을 간편하게 할 수 있는 도구 Postman 을 설치해서 사용해보겠습니다.

Postman 설치 및 사용

Postman 은 macOS, Windows, Linux 에서 모두 사용 가능한 프로그램입니다. 설치는 공식 홈페이지 (https://www.getpostman.com/) 에서 인스톨러를 통하여 설치 할 수 있습니다.

Postman 을 설치하고 나면, 다음과 같은 창이 나타납니다:

여기서 GET 부분을 클릭하여 메소드를 선택하고, 그 우측의 텍스트 박스에 주소를 입력후 Send 버튼을 누르면 요청을 시작 할 수 있습니다.

한번, POST /api/posts 에 요청을 해볼까요?

잘 작동하지요? 이 외에도 다른 API 를 입력하여 테스트 해보세요:

컨트롤러 파일 작성

라우트를 작성 할 때는, 라우트를 설정하는 부분에서 파라미터쪽에 화살표 함수 문법을 사용하여 라우트 처리 함수를 바로 선언하여 설정을 할 수 있습니다.

posts.get('/', (ctx) => { ... });

하지만, 만약에 각 라우트 처리 함수의 코드 길이가 길어진다면, 라우터 설정을 한눈에 보기가 힘들어지겠죠. 그렇기 때문에, 이 라우트 처리 함수들을 따로 다른 파일로 분리시켜서 관리 할 수도 있습니다. 이 라우트 처리 함수만 모여진 파일을, 컨트롤러라고 부릅니다. 컨트롤러에서는, 백엔드의 기능이 구현됩니다.

현재 우리는 아직 데이터베이스 연결을 하지 않았으므로, 자바스크립트의 배열 기능만을 사용해 임시로 기능을 구현해보도록 하겠습니다.

API 의 기능을 본격적으로 구현하기 전에, 우리는 koa-bodyparser 라는 미들웨어를 적용해야 합니다. 이 미들웨어는, POST/PUT/PATCH 등의 메소드의 Request Body 에 JSON 형식으로 데이터를 넣어주면 이를 파싱해서 서버측에서 사용 할 수 있도록 해줍니다.

다음 명령어를 통하여 패키지를 설치하세요:

$ yarn add koa-bodyparser

그 다음엔, 미들웨어를 불러와서 적용하세요. 주의하실 점은, router 를 설정하는 코드의 윗부분에서 설정해주어야합니다.

src/index.js

const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');

const api = require('./api');

const app = new Koa();
const router = new Router();

// 라우터 설정
router.use('/api', api.routes()); // api 라우트 적용

// 라우터 적용전에, bodyParser 적용
app.use(bodyParser());

// app 인스턴스에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());

app.listen(4000, () => {
  console.log('listening to port 4000');
});

src/api/posts/posts.ctrl.js 파일을 만든 다음에, 주석을 참고해가면서 다음 코드를 작성해보세요.

src/api/posts/posts.ctrl.js

let postId = 1; // id의 초기값 입니다

const posts = [
  {
    id: 1,
    title: '제목',
    body: '내용'
  }
];

/* 포스트 작성
   POST /api/posts
   { title, body } */
exports.write = (ctx) => {
  // REST API의 request body 는 ctx.request.body 에서 조회 할 수 있습니다.
  const {
    title,
    body
  } = ctx.request.body;

  postId += 1; // 기존의 postId 값에 1을 더합니다

  const post = { id: postId, title, body };
  posts.push(post);
  ctx.body = post;
};

/* 포스트 목록 조회
   GET /api/posts */
exports.list = (ctx) => {
  ctx.body = posts;
};

/* 특정 포스트 조회
   GET /api/posts/:id */
exports.read = (ctx) => {
  const { id } = ctx.params;

  // 주어진 id 값으로 포스트를 찾습니다
  // 파라미터로 받아온 값은 문자열 형식이니, 파라미터를 숫자로 변환하거나,
  // 비교 할 p.id 값을 문자열로 변경해야합니다.
  const post = posts.find(p => p.id.toString() === id);

  // 포스트가 없을 경우 에러를 반환합니다.
  if (!post) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.'
    };
    return;
  }

  ctx.body = post;
};

/* 특정 포스트 제거
   DELETE /api/posts/:id */
exports.remove = (ctx) => {
  const { id } = ctx.params;

  // 해당 id 를 가진 post 가 몇번째인지 확인합니다
  const index = posts.findIndex(p => p.id.toString() === id);

  // 포스트가 없을 경우 에러를 반환합니다.
  if (index === -1) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.'
    };
    return;
  }

  // index 번째 아이템을 제거합니다.
  posts.splice(index, 1);
  ctx.status = 204; // No Content
};

/* 포스트 수정 (교체)
  PUT /api/posts/:id
  { title, body } */
exports.replace = (ctx) => {
  // PUT 메소드는 전체 포스트 정보를 입력하여 데이터를 통째로 교체 할 때에 사용됩니다.
  const { id } = ctx.params;

  // 해당 id 를 가진 post 가 몇번째인지 확인합니다
  const index = posts.findIndex(p => p.id.toString() === id);

  // 포스트가 없을 경우 에러를 반환합니다.
  if (index === -1) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.'
    };
    return;
  }

  // 전체 객체를 덮어씌웁니다.
  // 따라서, id 를 제외한 기존 정보를 날리고, 객체를 새로 생성합니다.
  posts[index] = {
    id,
    ...ctx.request.body
  };
  ctx.body = posts[index];
};

/* 포스트 수정 (특정 필드 변경)
   PATCH /api/posts/:id
   { title, body } */
exports.update = (ctx) => {
  // PATCH 메소드는 주어진 필드만 교체합니다.
  const { id } = ctx.params;

  // 해당 id 를 가진 post 가 몇번째인지 확인합니다
  const index = posts.findIndex(p => p.id.toString() === id);

  // 포스트가 없을 경우 에러를 반환합니다.
  if (index === -1) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.'
    };
    return;
  }

  // 기존의 값에 정보를 덮어 씌웁니다.
  posts[index] = {
    ...posts[index],
    ...ctx.request.body
  };
  ctx.body = posts[index];
};

컨트롤러를 만들면서, exports.이름 = … 의 방식으로 함수를 내보내주었는데요, 이렇게 내보낸 코드는 불러올 때 다음의 형식으로 불러올 수 있습니다:

const 모듈명 = require('파일명');
모듈명.이름();

즉, 방금 만든 posts.ctrl 파일을 불러오면 다음 객체를 불러오게 되죠:

{ write: [Function],
  list: [Function],
  read: [Function],
  remove: [Function],
  replace: [Function],
  update: [Function] }

replace 와 update 는, 서로 용도는 비슷하지만 구현 방식이 다릅니다. PUT 메소드는, 객체를 전체적으로 대체해주어야 하고, update 는, 기존의 값을 유지하면서 전달받은 값을 덮어씌워줘야 합니다.

이제 우리가 만든 컨트롤러 함수들을 각 라우트에 연결해보세요.

src/api/posts/index.js

const Router = require('koa-router');
const postsCtrl = require('./posts.ctrl');

const posts = new Router();

posts.get('/', postsCtrl.list);
posts.post('/', postsCtrl.write);
posts.get('/:id', postsCtrl.read);
posts.delete('/:id', postsCtrl.remove);
posts.put('/:id', postsCtrl.replace);
posts.patch('/:id', postsCtrl.update);

module.exports = posts;

이제, posts 라우터가 완성 되었습니다.

list, read, remove 를 제외한 API들은, 요청을 할 때 Request Body 를 필요로하는데요, Postman 에서 이 값을 어떻게 넣는지 알아봅시다.

포스트맨에서 POST 선택하면 다음과 같이 body 부분이 활성화 됩니다. body 탭을 선택후, raw 옵션을 클릭한 다음에, 주황색으로 나타나는 데이터의 타입은 JSON 으로 설정하세요.

그리고, 하단의 텍스트박스에 위 형식처럼 JSON 객체를 작성하고 Send 를 눌러보세요.

POST 요청을 성공하면 위와 같이 서버가 응답을 합니다.

포스트 등록이 정말 성공적으로 되었는지 확인 해볼까요?

GET /api/posts 에 요청을 해보세요.

[
    {
        "id": 1,
        "title": "제목",
        "body": "내용"
    },
    {
        "id": 2,
        "title": "test",
        "body": "test"
    }
]

등록이 성공적으로 되었다면, 우리가 방금 등록한 포스트가 list 함수로 조회 될 것입니다.

우리가 구현한 update 와 replace 함수는, 용도는 비슷하지만, 구현 방식이 다릅니다. update (PATCH) 의 경우엔, 기존의 값은 유지시키면서 새 값을 덮어씌우는 반면, replace (PUT) 는 Request Body 로 받은 값이 id 를 제외한 모든 값을 대체하게 됩니다.

한번, 직접 호출을 해볼까요? 다음 요청을 Postman 을 통하여 실행해보세요:

# 요청
PATCH http://localhost:4000/api/posts/1
{
    "title": "변경됨"
}

# 결과
{
    "id": 1,
    "title": "변경됨",
    "body": "내용"
}

PATCH 로 했을때는, 기존의 body 내용이 유지되며, Request Body 로 전달한 값만 변경이 되었습니다.

반면 PUT으로 하면 어떨까요?

# 요청
PUT http://localhost:4000/api/posts/1
{
    "title": "변경됨"
}

# 결과
{
    "id": "1",
    "title": "변경됨"
}

기존의 body 가 사라져버렸습니다. 따라서, 만약에 포스트 수정 API 를 PUT 을 사용하여 구현해야 하는 경우엔, 모든 필드가 있는지 검증하는 작업이 필요합니다.

results matching ""

    No results matching ""