TIL 22-05-16

thisisyjin·2022년 5월 16일
0

TIL 📝

목록 보기
49/113

React.js

Today I Learned ... react.js

🙋‍♂️ Reference Book

🙋‍ My Dev Blog


CH 21. 백엔드 프로그래밍 (2)

  • Node.js의 Koa 프레임워크
  • REST API

REST API

  • 웹 어플리케이션에서는 DB에 정보를 입력하고 읽어와야 한다.
  • 만약 웹브라우저가 직접 DB에 접속해서 데이터를 변경한다면 보안상 문제가 될 것임.
    -> REST API를 만들어서 사용함.

클라이언트가 서버에 데이터를 생성, 조회, 삭제, 업데이트 요청시
REST API는 요청 종류에 따라 다른 HTTP 메서드를 사용함.

GETPOSTDELETEPUTPATCH
데이터 조회데이터 등록 or 인증작업데이터 삭제데이터 통째로 교체특정 필드 수정

REST API 설계 시 API 주소메서드에 따라 어떤 역할을 하는지 쉽게 파악할 수 있도록 작성해야 함.

예>

  • POST /posts = 포스트 작성
  • GET /posts = 포스트 목록 조회
  • GET /posts/:id = 특정 포스트 조회
  • POST /posts/:id/comments = 특정 포스트에 댓글 등록

라우트 모듈화

  • 프로젝트를 진행하다 보면 여러 종류의 라우트를 만든다.
  • 각 라우트를 모두 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 라우터를 생성함.
(/tesst 라는 path에 있으면 'test 성공'이라는 문자열 출력

🔻 src/index.js 파일 수정.

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

const api = require('./api');
// 🔺 src/api/index.js를 모듈로 가져옴

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');
});

-> app.use()는 미들웨어 함수를 등록함 (ctx, next를 인자로 갖는 함수)

src/index.js에서는 /api path 안에 api라우트를 적용했으므로,
/api /test가 된다.
아래와 같이 api/test 로 접속하면 설정했던 문구가 뜬다.

posts 라우트 생성

  • api 라우트 내부에 posts 라우트를 생성.
  • 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/:id 로, 뒤에 붙은 params를 id라는 이름으로 저장함.
  • 즉, /posts/10의 경우에는 ctx.params의 값이 id:10 과 같이 나오는 것.

src/api/inex.js 수정

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

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

module.exports = api;

routes

posts 라우트의 여러 종류의 라우트를 설정한 후에, printInfo() 함수를 호출하게 설정함.

-> 참고로, get,post,delete 등 메서드 함수의 두번째 인자로 들어가는 함수는 호출부()를 적지 않음.
(path에 따라 조건부로 실행되어야 하므로)

  • printInfo 함수는 문자열이 아닌 JSON 객체를 반환하도록 설정함. (현재 요청의 메서드/경로/params를 담음)

결과

JSON 형태로 출력됨.

📁 참고 - 지금까지의 파일 구조

⚡️ 정리

koa 인스턴스를 'app'이라하고, koa-router의 인스턴스를 'api'라고 할 때,
라우터 생성api.get('/', 미들웨어함수)
라우터 적용app.use(router.routes()).use(router.allowedMethods())
라우터 중첩(모듈화)는 api.use('/'. route명.routes())


Postman

  • REST API request 테스트를 간편하게 할 수 있는 프로그램.
    공식 사이트에서 다운로드 가능.

프로그램 설치 후 로그인을 완료하면 아래와 같은 창이 나타남.

URL 입력 창에 http://localhost:4000/api/posts 를 입력하고, send를 눌러 요청함.

아래와 같이 데이터가 그대로 받아와짐.

이번에는 posts 뒤에 params(id)를 붙여서 요청해보면 아래와 같은 결과가 나옴.


컨트롤러 파일 작성

  • 라우트를 작성하는 과정에서 특정 경로에 미들웨어 등록시, 두번째 인자에 함수를 선언하여 바로 넣어줄 수 있음.
router.get('/', ctx => {});
  • 하지만 위와 같이 작성하면 라우터 설정을 한눈에 보기 힘들어짐.
    -> 따라서, 라우트 처리 함수들은 다른 파일로 따로 분리하여 관리 = 컨트롤러 파일

라우트 처리 함수(=미들웨어 함수)를 모아놓은 파일을 컨트롤러 라고 함.

  • API 기능 구현 전에, koa-bodyparser 미들웨어를 적용함.
    -> post/put/patch 같은 메서드의 request body에 JSON 형식으로 데이터를 넣어주면,
    이를 파싱해서 서버에서 사용 가능하게 함.

koa-bodyparser 설치

$ yarn add koa-bodyparser

src/index.js 수정

-> 미들웨어를 불러와 적용함.

단, router 적용(=app.use)하는 윗부분에서 미들웨어를 불러와야 함.

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 라우트 적용

// 🔻 라우터 적용 전에 미들웨어를 적용해야 함
app.use(bodyParser());

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

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

그리고, /posts 경로에 posts.ctrl.js 파일을 생성한 후 아래와 같이 입력.

/posts/posts.ctrl.js 생성

let postId = 1;

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

// 1. 포스트 작성 = POST /api/posts { title, body }
exports.write = (ctx) => {
  // ctx.request.body에서 REST API의 요청 body 조회 가능.
  const { title, body } = ctx.request.body;
  postId += 1;
  const post = { id: postId, title, body };
  posts.push(post);
  ctx.body = post;
};

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

// 3. 특정 포스트 조회 = GET /api/posts/:id
exports.read = (ctx) => {
  const { id } = ctx.params;
  const post = posts.find((p) => p.id.toString() === id);
  if (!post) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.',
    };
    return;
  }
  ctx.body = post;
};

// 4. 특정 포스트 제거 = DELETE /api/posts/:id
exports.remove = (ctx) => {
  const { id } = ctx.params;
  const index = posts.findIndex((p) => p.id.toString() === id);
  if (index === -1) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.',
    };
    return;
  }
  posts.splice(index, 1);
  ctx.status = 204; // No Content
};

// 5. 포스트 수정 (교체) = PUT /api/posts/:id
exports.replace = (ctx) => {
  const { id } = ctx.params;
  const index = posts.findIndex((p) => p.id.toString() === id);
  if (index === -1) {
    ctx.status = 404;
    ctx.body = {
      message: '포스트가 존재하지 않습니다.',
    };
    return;
  }
  posts[index] = { id, ...ctx.request.body };

  ctx.body = posts[index];
};

// 6. 포스트 수정 (특정 필드) = PATCH /api/posts/:id
exports.update = (ctx) => {
  const { id } = ctx.params;
  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];
};

1. params는 모두 string으로 저장된다.
즉, id=10과 같은 경우에도 '10'으로 저장되므로,
특정 아이디인 포스트를 찾으려면 posts.find(p => p.id.toString() === id);
와 같이 찾아야 함.

2. 추가시 push, 삭제시 splice
posts 배열에 post를 추가할때는 push(post)를 하고,
posts 배열에서 삭제할 때는 id를 찾은 후, 인덱스를 구하고 (array.findIndex)
splice(index, 1) 해줌.

3. 수정시 배열 값 수정
posts 배열에서 id에 일치하는 인덱스를 찾고,
해당 아이템을 대입연산자를 이용하여 수정함. (+ spread)
->

posts[index] = { id, ...ctx.request.body } // 전체 교체

posts[index] = { ... posts[index], ...ctx.request.body } // 특정 필드만 교체

4. exports.__

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

// example
const controller = require('./posts.ctrl');

// use
controller.list
controller.remove
controller.replace ...

컨트롤러 함수 연결

= 각 컨트롤러 함수들을 라우트에 연결시키기.

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.get의 두번째 인자로는 미들웨어 함수가 들어감.
    -> 각 함수를 넣어줌.

list, read, remove를 제외한 API 들은 요청시 request body가 필요하다.
(write, replace, update의 경우 = 즉 POST, PUT, PATCH)

PostMan에서 url 좌측에 메서드를 POST로 바꾸면, 하단 Body 부분이 활성화 된다.
raw를 선택한 후 아래와 같이 JSON을 입력한다.

입력한 후 Send 버튼을 누르고 POST 요청에 성공하면 서버가 응답하게 됨. (하단 response Body 주목)

참고 - 반드시 Type 설정을 JSON으로 한 다음 Send를 누르자.
그렇지 않으면 디폴트 타입인 text로 request가 되어 제대로 적용되지 않음!

🔻 PATCH로 특정 필드만 변경 가능.

🔻 반면에 PUT으로 title 필드만 적어주면, 덮어써서 body 필드가 사라져버림.

-> 따라서, PUT으로 수정시에는 모든 필드가 다 있는지 검증해야 함!


다음 포스팅

  • mongoDB
profile
기억은 한계가 있지만, 기록은 한계가 없다.

0개의 댓글