Today I Learned ... react.js
🙋♂️ *Reference Lecture
🙋 My Dev Blog
CH 21. 백엔드 프로그래밍
- Node.js의 Koa 프레임워크
$ mkdir blog
$ cd blog
$ mkdir blog-backend
$ cd blog-backend
$ yarn init -y
$ yarn add koa
$ yarn add --dev eslint
$ yarn run eslint --init
yarn run eslint --init
을 해준다.위와 같이하고 나면, 디렉터리에 아래 파일이 생성된다.
.eslintrc.json
{
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
}
}
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}
$ yarn add eslint-config-prettier
.eslintrc.json (수정)
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": ["eslint:recommended", "prettier"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {}
}
blog-backend 디렉터리에 /src 디렉터리를 생성하고, index.js 파일에서 테스트 해보자.
const hello = "hello";
위와 같이 변수를 선언하고 사용하지 않으면, ESLint에서는 에러로 간주한다.
위와 같은 규칙을 끄기 위해서는 .eslintrc.json
의 'rules' 필드를 아래와 같이 수정해주면 된다.
...
"rules": {
"no-unused-vars": "warn",
"no-console": "off"
}
참고로, ESLint Rules 에 대한 설명은 공식문서를 참고하자.
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');
});
참고 - 왜 import - from이 아닌
require
을 사용하는지>
- 이전에는 CRA환경이므로 바벨이 설정이 되어있어 es6 문법인 import를 사용 가능했다.
그러나 이번에는 nodeJS 환경에서 진행되므로, commonJS인 require을 사용해야 한다.
node src
-> 파일명이 index.js
이므로 폴더명만 적으면 알아서 실행됨.
-> app.listen()이 실행되어 두번째 인자인 콜백함수가 실행됨. (console.log)
이제 웹 브라우저로 localhost:4000에 접속해보면 아래와 같이 hello world 라는 문구가 보임.
Koa 어플리케이션은 미들웨어의 배열로 구성되어 있음.
위에서 사용했던 app.use()
함수는 미들웨어 함수를 어플리케이션에 등록하는 기능임.
app.use((ctx) => {
ctx.body = 'hello world';
});
미들웨어 함수
- 미들웨어 함수는 두개의 파라미터를 받음.
- 첫번째 파라미터
ctx
는 웹 요청(request)과 응답(response)에 대한 정보를 지니고,- 두번째 파라미터
next
는 현재 처리중인 미들웨어의 다음 미들웨어를 호출하는 함수임.
미들웨어를 등록하고 next 함수를 호출하지 않으면,그 다음 미들웨어를 처리하지 않음.
✅ 참고 - next를 사용하지 않을 때
미들웨어를 처리할 필요가 없는 라우트 미들웨어를 나중에 설정할 때 주로 next를 생략함.
다음은 현재 요청을 받은 주소와 숫자를 기록하는 두개의 미들웨어를 담은 코드임.
index.js (수정)
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');
});
다시 node src
를 실행해보면 아래와 같다.
크롬 브라우저는 사이트의 아이콘 파일인 /favicon.ico 파일을 서버에 요청하기 때문에, /경로와 더불어 /favicon.ico 경로도 나타나게 됨.
console.log(ctx.url);
이번에는 app.use()에서 첫번째 next()를 주석처리한 후 결과를 살펴보자.
이번에는 첫번째 미들웨어의 결과인 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();
});
...
request의 경로에 authorized=1
이라는 쿼리 파라미터가 없을 때는, 이후 미들웨어를 처리하지 않음.
-> return으로 use() 함수를 빠져나와서 다음 코드인 next()가 실행되지 않음.
authorized=1
로 적어주면?❗️ 참고 - 나중에는 웹 요청(request)의 쿠키 or 헤더를 통해 처리함.
next() 함수 호출시 Promise 를 반환한다.
-> Promise는 다음에 처리해야 할 미들웨어가 끝나야 완료된다.
next()를 호출한 다음에 then 메서드를 사용하여
Promise가 끝나면 콘솔에 end를 출력하도록 해보자.
✅ 참고 - Promise 객체 메소드 체이닝
.then
은 Promise를 반환하므로 계속 연속해서 이을 수 있다.
next()
도 Promise를 반환하므로 마찬가지임!
index.js
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!');
})
});
...
Express
는 async/await을 적용할 수는 있지만, 제대로 에러를 잡아내지 못할 수도 있다.
하지만, Koa
는 async/await을 정식으로 지원하기 때문에 편하게 사용 가능!
index.js (수정)
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!');
});
...
-> 기존 코드와 동일하게 작동하지만, async-await 으로 작성한 코드이다.
서버 코드를 변경할 때 마다 매번 ctrl+c로 빠져나온 후, node src를 입력하는 것은 번거로운 일이다.
nodemon
이라는 도구를 사용하면 코드를 변경할 때 마다 서버를 자동으로 재시작 할 수 있다.
$ yarn add --dev nodemon
package.json
"scripts": {
"start": "node src",
"start:dev": "nodemon --watch src/ src/index.js"
}
--watch
옵션은 해당 디렉터리를 관찰하라는 뜻.
start
는 재시작이 필요 없을 때,
start:dev
는 재시작이 필요할 때 사용하면 된다.
$ yarn start:dev
파일을 저장할 때 마다 터미널에 아래와 같이 나옴.
즉, 수동으로 재시작 할 필요가 없어짐!
이전에는 react-router-dom 라이브러리를 사용했으나,
Koa
를 사용할때에도 다른 주소로 요청이 들어올 경우 다른 작업을 처리하도록 라우팅이 필요함.
Express 와는 달리 Koa에는 라우팅 기능이 내장되어 있지 않으므로,
koa-router
모듈을 설치해야 함.
yarn add koa-router
index.js
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', (ctx) => {
ctx.body = '소개';
});
// app 인스턴스에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log('Listening to port 4000');
});
/ 경로로 들어오면 '홈' 문구를 띄우고,
/about 경로로 들어오면 '소개' 문구를 띄우게 함.
app.use 의 인자에
router.routes()).use(router.allowedMethods()
를 넣어줌.
.get
의 첫번째 인자로는 경로(path)를 넣고, 두번째 인자로는 미들웨어 함수를 넣는다.
-> get 키워드는 HTTP 메서드를 의미함.
라우터의 파라미터 설정 시 /about/:name
과 경로를 설정함.
+) 파라미터가 있을수도 있고, 없을수도 있다면
/about/:name?
과 같이 ?를 파라미터 뒤에 붙여줌.
-> 설정한 파라미터는 ctx.params
객체를 통해 조회할 수 있다.
index.js
...
router.get('/about/:name?', (ctx) => {
const { name } = ctx.params;
ctx.body = name ? `${name}의 소개` : '소개';
});
...
ctx.params에 name이라는 필드로 저장되어 있을 것임.
즉, params가 존재하면 name 변수에 값이 있을 것이고, 없다면 (/about 이라면) 값이 존재하지 않을 것이다.
-> 삼항 연산자를 이용하여 ctx.body
에 조건부로 값을 할당한다.
param이 없을 때 (/about)
param이 있을 때 (/about/yjin)
ctx.query
객체의 값을 이용하면 된다.router.get('/posts', (ctx) => {
const { id } = ctx.query;
ctx.body = id ? `포스트 #${id}` : '포스트 id가 없습니다.';
});
id가 없을 때 (/posts)
id가 있을 때 (/posts?id=2)
다음 포스팅 - REST API
- 라우트 모듈화
- mongoDB / mongoose