21. 백엔드 프로그래밍 : Node.js의 Koa 프레임워크

히치키치·2022년 1월 7일
0

React_Advance

목록 보기
5/9
post-thumbnail

✔ Koa 기본 사용법

1. 서버 띄우기

  • 서버 포트를 4000번 열고, 서버에 접속하면 "hello world" 텍스트 반환

코드 작성 : index.js

const Koa = require('koa');

const app = new Koa();

app.use((ctx) => {
  ctx.body = 'hello world';
});

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

서버 실행

실행화면

2. 미들웨어

app.use 함수를 사용해 미들웨어 함수를 애플리케이션에 등록

미들웨어 함수 구조

(ctx,next)=>{
}

ctx : 웹 요청과 응답에 대한 정보 지님
next : 현재 처리 중인 미들웨어의 다음 미들웨어를 호출하는 함수
(미들웨어 등록 후 next 함수 호출하지 않으면 그다음 미들웨어 처리 X)
app.use 사용해 등록되는 순서대로 사용되는 미들웨어

요청 받은 주소와 정해준 숫자를 기록하는 두 개의 미들웨어

const Koa = require('koa');

const app = new Koa();

app.use((ctx, next) => {
  console.log(ctx.url);
  console.log(1);
  next();
});

app.use((ctx, next) => {
  console.log(2);
  next();
});

app.use((ctx) => {
  ctx.body = 'hello world';
});

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

사용자가 웹페이지에 들어가면 해당 사이트의 아이콘 파일을 서버에 요청하기 때문에 터미널 결과에 / 경로도 나타나고 /favicon.ico 경로도 나타남

첫번째 미들 웨어 다음의 미들웨어 호출 주석 처리해 그 이후 미들웨어 무시함

//첫번째 미들웨어 - 다음 미들웨어 호출 주석 처리
app.use((ctx, next) => {
  console.log(ctx.url);
  console.log(1);
  //next();
});

요청경로에 authorized=1 쿼리 파라미터 포함된 경우에만 미들웨어 처리

  • 쿼리 파라미터는 문자열임 -> 문자열 형태로 비교 해야함
const Koa = require('koa');

const app = new Koa();

app.use((ctx, next) => {
  console.log(ctx.url);
  console.log(1);
  if (ctx.query.authorized !== '1') {
    //숫자가 아님 문자 형태로 비교
    ctx.status = 401; //unauthorized
    return;
  }
  next();
});

app.use((ctx, next) => {
  console.log(2);
  next();
});

app.use((ctx) => {
  ctx.body = 'hello world';
});

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

http://localhost:4000/ 사용

http://localhost:4000/?authorized=1 사용

3. next 함수 호출 시 Promise 반환

  • Koa와 Express의 차별화된 점
  • next 함수가 반환하는 Promise는 다음에 처리해야 할 미들웨어가 끝나야 완료됨

next 함수 호출 이후에 then 사용해 Promise가 끝난 다음 콘솔에 END 기록

2를 출력하는 다음 미들웨어까지 모두 작동이 완료된 후 첫번째 미들웨어인 Promise가 끝나면서 then이 출력됨

http://localhost:4000/?authorized=1

const Koa = require('koa');

const app = new Koa();

app.use((ctx, next) => {
  console.log(ctx.url);
  console.log(1);
  if (ctx.query.authorized !== '1') {
    //숫자가 아님 문자 형태로 비교
    ctx.status = 401; //unauthorized
    return;
  }

  next().then(() => {
    console.log('END');
  });
});

(...생략...)

4. async/await 사용

app.use(async (ctx, next) => {
  console.log(ctx.url);
  console.log(1);
  if (ctx.query.authorized !== '1') {
    //숫자가 아님 문자 형태로 비교
    ctx.status = 401; //unauthorized
    return;
  }
  await next();
  console.log('end');
});

(...생략...)

http://localhost:4000/?authorized=1

✔ nodemon 사용

  • nodemon 사용하여 코드 변경할 때마다 서버 자동으로 재시작

1. 개발용 의존 모듈 설치

yarn add --dev nodemon

2. package.json에 script 작성

{
  "name": "blog-backend",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "eslint-config-prettier": "^8.3.0",
    "koa": "^2.13.4"
  },
  "devDependencies": {
    "eslint": "^8.6.0",
    "nodemon": "^2.0.15"
  },
  "scripts": {
    "start": "node src",
    "start:dev":"nodemon --watch src/ src/index.js"
    /* src 디렉토리 주시하다가 해당 디렉토리에 변경 발생시, 
    src/index.js 파일 재시작 */
  }
}

3. 서버 시작 명령어

yarn start # 재시작 필요 없는 경우
yarn start:dev # 재시작 필요한 경우

index.js

const Koa = require('koa');
const app = new Koa();
app.listen(4000, () => {
  console.log('Listen to port 4000');
});

서버 재시작 후 터미널 결과

✔ koa-router 사용

  • 다른 주소로 요청이 들어올 경우 다른 작업을 처리할 수 있도록 라우터 사용

1. koa-router 설치

yarn add koa-router

2. 라우터 적용

  • 라우터 불러와 인스턴스 생성
  • 해당 라우트 경로와 이에 따라 실행할 미들웨어 함수 설정
  • 해당 라우트에서 사용할 HTTTP 메서드 선택
const Koa = require('koa');
//koa-router 불러오기
const Router = require('koa-router');

const app = new Koa();
//Router 인스턴스 생성
const router = new Router();

//라우터 설정 router.get ( )
//첫번째 인자 : 라우트 경로, 두번째 인자 : 해당 라우트에 적용할 미들웨어 함수
//get : 해당 라우트에서 사용할 HTTP 메서드

router.get('/', (ctx) => {
  ctx.body = '홈';
});

router.get('/about', (ctx) => {
  ctx.body = '소개';
});

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

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


3. 라우트 파라미터 / 쿼리

라우트 파라미터
: 처리할 작업의 카테고리, 특정 ID/데이터 조회

  • 콜론 (:) 사용해 about/:{name} 형식으로 라우트 경로 설정
  • 물음표 (?) 사용해 파라미터 존재 또는 미존재 경우 about/:{name}? 형식으로 설정
  • ctx.params{name} 값 조회

URL 쿼리
: 옵션에 관련된 정보 받아옴
: 조건 또는 정렬 기준에 따른 여러 항목 리스팅

  • /posts/?id=10 같은 형식으로 요청
  • 쿼리 문자열을 자동으로 객체 형태로 파싱해 별도의 파싱 함수 필요 X
  • 문자열 형태의 쿼리 문자열 조회 해야할 때 ctx.queryString 사용
const Koa = require('koa');
const Router = require('koa-router');

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

router.get('/', (ctx) => {
  ctx.body = '홈';
});

router.get('/about/:name?', (ctx) => {
  const { name } = ctx.params;
  console.log(name);
  //name 존재 유무에 따라 다른 결과 출력
  ctx.body = name ? `${name}의 소개` : '소개';
});

router.get('/posts', (ctx) => {
  const { id } = ctx.query;
  //id 존재 유무에 따라 다른 결과 출력
  ctx.body = id ? `포스트 #${id}` : '포스트 아이디가 없습니다.';
});

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

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

http://localhost:4000/about/react

http://localhost:4000/posts

http://localhost:4000/posts?id=10

4. REST API

클라이언트가 서버에 자신이 데이터를 조회/생성/삭제/업데이트 하겠다고 요청하면, 서버는 필요한 로직에 따라 DB에 접근해 작업 처리

메서드 종류에 따라 get, post, delete, put, patch 사용해 라우터에서 각 메서드의 요청 처리

5. 라우트 모듈화

라우터를 여러 파일에 분리해 작성하고 불어와서 적용하기

src/api/index.js

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

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

//라우터 내보내기
module.exports = 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();

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

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

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

/api/test 라우트 작동

6. posts 라우트 생성

  • api 라우트 내부에 posts 라우트 생성
    (api 디렉터리에 posts 디렉터리 생성 후 index.js 파일 생성)

posts/index.js

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

//posts 라우트 생성
const posts = new Router();

const printInfo = (ctx) => {
  //문자열이 아닌 JSON 객체 반환
  ctx.body = {
    /*현재 요청의 메서드, 경로, 파라미터 */
    method: ctx.method,
    path: ctx.path,
    parmas: ctx.params,
  };
};

//여러 종류의 라우트 설정 & 모두 printInfo 함수 실행
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;
  • api 라우트에 posts 라우트 연결
const Router = require('koa-router');

//posts 라우트 불러와 설정
const posts = require('./posts');
const api = new Router();

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

//라우터 내보내기
module.exports = api;

http://localhost:4000/api/posts

  • GET 메서드 사용 API : 웹 브라우저 주소 입력으로 테스팅 가능
  • POST, DELETE, PUT, PATCH 메서드 : 자바스크립트로 호출
    (Postman 프로그램 사용)

postman을 사용해 HTTP Method test 실시 결과

DELETEPUTPATCH


7. 컨트롤러 파일 작성

컨트롤러 : 라우트 처리 함수만 모아 놓은 파일

  • koa-bodyparser 미들웨어 적용
  • 미들웨어는 POST/PUT/PATCH 메서드의 request body에 JSON 형식으로 데이터 넣으면 이를 파싱해 서버에서 사용 가능하게 함
yarn add koa-bodyparser

src/index.js

  • 미들웨어 불러와 적용 (router 적용 코드 위에 작성)
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');
});

posts/posts.ctrl.js

  • exports.이름=... 형식으로 함수 내보내기
let postId = 1; // id의 초깃값입니다.

// posts 배열 초기 데이터
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];
};

src/api/posts/index.js

  • const 모듈이름 = require("파일이름"); 모듈이름.이름(); 형식으로 불러오기
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;

POST로 API 요청

GET으로 API 요청

PUT으로 API 요청

  • 기존의 body 사라짐
  • 모든 필드가 다 있는지 검증하는 작업 필요

0개의 댓글