관리 메뉴

거니의 velog

(9) 백엔드 프로그래밍: Node.js의 Koa 프레임워크 3 본문

React_백엔드 프로그래밍

(9) 백엔드 프로그래밍: Node.js의 Koa 프레임워크 3

Unlimited00 2024. 2. 20. 13:12

3. Koa 기본 사용법

(1) 서버 띄우기

* 먼저 서버를 여는 방법부터 알아보자. 기존 index.js 파일에 작성한 코드를 지우고, 다음 코드를 입력하자.

const Koa = require('koa');

const app = new Koa();

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

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

* 서버를 포트 4000번으로 열고, 서버에 접속하면 'hello world' 라는 텍스트를 반환하도록 설정했다.

서버를 한 번 실행해 보자.

$ node src

* 원래 node를 통해 자바스크립트 파일을 실행할 때는 node src/index.js와 같이 전체 경로를 입력하는 것이 맞지만, index.js 파일은 예외로 디렉터리까지만 입력해도 실행할 수 있다. index.js를 해당 디렉터리를 대표하는 파일이라고 생각하면 된다.

* 이제 웹 브라우저로 http://localhost:4000/ 에 접속해 보자.

* hello world 가 잘 나타났다.


(2) 미들웨어

* Koa 애플리케이션은 미들웨어의 배열로 구성되어 있다. 조금 전 코드에서 app.use 함수를 사용했는데, 이 함수는 미들웨어 함수를 애플리케이션에 등록한다.

* 미들웨어 함수는 다음과 같은 구조로 이루어져 있다.

(ctx, next) => {
	
}

* Koa 의 미들웨어 함수는 두 개의 파라미터를 받는다. 첫 번째 파라미터는 조금 전에도 사용한 ctx라는 값이고, 두 번째 파라미터는 next이다.

* ctx는 Context의 줄임말로 웹 요청과 응답에 관한 정보를 지니고 있다. next는 현재 처리 중인 미들웨어의 다음 미들웨어를 호출하는 함수이다. 미들웨어를 등록하고 next 함수를 호출하지 않으면, 그다음 미들웨어를 처리하지 않는다.

* 만약 미들웨어에서 next를 사용하지 않으면 ctx => {}와 같은 형태로 파라미터에 next를 설정하지 않아도 괜찮다. 주로 다음 미들웨어를 처리할 필요가 없는 라우트 미들웨어를 나중에 설정할 때 이러한 구조로 next를 생략하여 미들웨어를 작성한다.

* 미들웨어는 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('Listening to port 4000');
});

* 실행 중인 서버를 Ctrl + C를 눌러서 종료한 뒤, 다시 node src 명령어를 입력하여 실행해 주자. 그리고 다시 http://localhost:4000/을 열어 보면 서버가 실행되고 있는 터미널에 다음과 같은 결과물이 나타날 것이다.

* 크롬 브라우저는 사용자가 웹 페이지에 들어가면 해당 사이트의 아이콘 파일인 /favicon.ico 파일을 서버에 요청하기 때문에 결과에 / 경로도 나타나고 /favicon.ico 경로도 나타난다.

* 이번에는 첫 번째 미들웨어에서 호출하던 next 함수를 주석처리해 보자.

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('Listening to port 4000');
});

* 그리고 서버를 재시작한 뒤 조금 전 열었던 페이지를 새로고침해 보자. 어떤 결과가 나타날까?

* next를 호출하지 않으니 첫 번째 미들웨어까지만 실행하고 그 아래에 있는 미들웨어는 모두 무시되었다. 이런 속성을 사용하여 조건부로 다음 미들웨어 처리를 무시하게 만들 수 있는데, 한번 시도해 보자.
다음 코드에서는 요청 경로에 authorize=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('Listening to port 4000');
});

* 쿼리 파라미터는 문자열이기 때문에 비교할 때는 반드시 문자열 형태로 비교해야 한다.

* 이제 서버를 재시작한 뒤 다음 링크에 들어가서 어떤 결과가 나타나는지 확인해 보자.

- http://localhost:4000/

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

* 지금은 단순히 주소의 쿼리 파라미터를 사용하여 조건부로 처리했지만, 나중에는 웹 요청의 쿠키 혹은 헤더를 통해 처리할 수도 있다.

[1] next 함수는 Promise를 반환

* next 함수를 호출하면 Promise를 반환한다. 이는 Koa가 Express와 차별화되는 부분이다. next 함수가 반환하는 Promise는 다음에 처리해야 할 미들웨어가 끝나야 완료된다. 다음과 같이 next 함수 호출 이후에 then을 사용하여 Promise가 끝난 다음 콘솔에 END를 기록하도록 수정해 보자.

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

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

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

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

* 서버를 재시작한 뒤 http://localhost:4000/?authorized=1 주소에 들어가 보자. 터미널에 어떤 결과가 나타날까?

* END가 잘 나타났다. (참고로 브라우저 버전에 따라 /favicon.ico 를 요청하지 않을 수도 있다. 하단의 /favicon.ico, 1이 나타나지 않아도 괜찮으니 신경 쓰지 않아도 된다.

[2] async/await 사용하기

* Koa는 async/await를 정식으로 지원하기 때문에 해당 문법을 아주 편하게 사용할 수 있다.

* 서버 사이드 렌더링을 할 때 사용했던 Express의 async/await 문법을 사용할 수 있지만, 오류를 처리하는
  부분이 제대로 작동하지 않을 수 있다. 백엔드 개발을 하면서 예상치 못한 에러를 제대로 잡아내려면
  express-async-errors라는 라이브러리를 따로 사용해야 한다.

* 기존 코드를 async/await를 사용하는 형태로 한번 수정해 보자.

const Koa = require('koa');

const app = new Koa();

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

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

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

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

* 서버를 재시작한 뒤 http://localhost:4000/?authorized=1 에 다시 들어가 보자. 이전과 똑같이 작동하면 된다.