2022. 4. 17. 16:43ㆍ책/리액트를 다루는 기술
사실 이전까지가 리액트 대부분의 기본 개념들이었다. 리액트 '자체'가 궁금했다면 21장까지만 읽어보면 된다. 그런데 웹 애플리케이션을 만들다 보면 백엔드 기술이 필요한 경우가 허다하다. 물론 여기선 DB설계 같은 어려운 개념을 다루진 않을 것이다.
1. Koa 프레임워크
서버 개발용 프레임워크. Express의 기존 개발 팀이 개발했다. Koa는 미들웨어 기능만 갖추고 있어 Express보다 훨씬 가볍고, async/await 문법을 정식으로 지원해서 비동기 작업 관리가 더 편하다. 사실 뭐가 됐건 취향껏 하면 되기 때문에 문제는 없다만, 이 책에선 Koa를 쓰기로 한다. (사실 Express 커뮤니티가 훨씬 더 커서 참고하기 쉬움)
2. Koa 기본 사용법
2.1 서버 띄우기
Express랑 거의 동일하다. 단지 app.use( (req, res, next) => { ... } )형태가 app.use( (ctx, next) => { ... } ) 형태로 바뀜.
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
// 포트 4000번으로 서버열어주기
app.listen(4000);
2.2 미들웨어
app.use를 사용해서 등록되는 순서대로 처리되며, (ctx, next) 파라미터를 받아서 동작한다. 당연히 미들웨어에서 next를 호출하지 않으면 다음 미들웨어로 넘어가지 않는다.
const Koa = require('koa');
const app = new Koa();
// 순차적 미들웨어 처리
// https://localhost:4000으로 접속하면
// /
// 하나
// 둘
// 콘솔창에 순서대로 찍힌다
app.use((ctx, next) => {
console.log(ctx.url); //접속한 url 경로를 띄워줌
console.log('하나');
next();
});
app.use((ctx, next) => {
console.log('둘');
next();
});
app.use(ctx => {
ctx.body = 'hello Koa';
});
// 포트 4000번으로 서버열어주기
app.listen(4000);
※ next
Express와 다르게 Koa의 next는 Promise를 반환한다. next 함수가 반환하는 Promise는 다음에 처리해야 할 미들웨어가 끝나면 완료된다. 즉, 아래 예시에서 콘솔창에 하나 → 둘 → 셋 이 출력된다.
app.use((ctx, next) => {
console.log('하나');
next().then(() => console.log('셋'));
});
app.use((ctx, next) => {
console.log('둘');
next();
});
또한 Promise라는 뜻은 Koa에선 async/await를 쓸 수도 있다는 말과 같다. 이 경우에도 역시 콘솔창에 하나 → 둘 → 셋 이 출력된다.
// async로 받아서
app.use(aysnc (ctx, next) => {
console.log('하나');
await next(); // await를 쓴다
console.log('셋');
});
app.use((ctx, next) => {
console.log('둘');
next();
});
2.3 라우터(koa-router)
1) 기본 사용법(파라미터, 쿼리 포함)
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// GET요청
router.get('/', ctx => {
ctx.body = '홈';
});
// 파라미터 받아오기
router.get('/about/:name?', ctx => {
const { name } = ctx.params; // params에 담겨있다
ctx.body = name ? `파마니터 : ${name}` : '그냥 소개';
});
// 쿼리스트링 꺼내오기
router.get('/posts', ctx => {
const { id } = ctx.query; // query에 담겨있다
ctx.body = id ? `포스트 ${id}` : '포스트 아이디가 없네요';
});
// app 인스턴스에 라우터 적용하기
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000);
http 요청중 가장 기본적인 get요청에 대해서만 보여줬지만, 어차피 post delete patch put요청 다 거기서 거기임. 대신 POST, PUT, PATCH 요청같은 경우 koa-bodyparser 미들웨어 적용이 필요하다. 그 이유는 post, put, patch 메서드의 경우 request body에 JSON 형식으로 데이터를 넣어 주게 되는데, Koa가 이걸 그대로 알아먹질 못해서 따로 미들웨어를 설치해서 파싱해줘야 알아먹기 때문이다.
2) 라우트 모듈화
프로젝트를 진행하다 보면 필연적으로 라우트 종류가 많아지게 된다. 게다가 각 요청별 라우트가 처리해야할 코드가 길어지면 보기도 힘들어진다. 이 때 적당히 여러 파일에 분리시켜서 작성하고 불러와 적용해야 깔끔한 코드 작성과 유지보수가 가능하다.
2.1) 라우트 분리
대단한건 아니고 별도의 디렉토리를 생성하고, 그 안에 적당한 파일을 만들어서 라우터를 작성하는 방식이다. 아래 예시는 다음과 같은 구조로 작성돼있다.
- localhost:4000/api 접속 : api폴더 > index.js에서 작성한 라우터대로 보여주기
- localhost:4000/api/posts 접속 : api폴더 > posts폴더 > index.js에서 작성한 라우터대로 보여주기
파일경로 : src/index.js
...
// api 경로로 접속하시려구요?
// api 폴더에 index.js 이름으로 라우터 따로 만든거 있는데
// 걔가 해주는 라우팅에 맞춰서 보여드림 ㅅㄱ
const api = require('./api'); // 사실 ./api/index.js 가 정확한 표현이긴 한데
router.use('/api', api.routes()); // index.js는 보통 생략가능해서 작성안함
// bodyparser 적용하기
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
app.use(router.routes()).use(router.allowedMethods());
파일 경로 : src/api/index.js
const Router = require('koa-router');
const api = new Router();
// /post 경로로 접속하시려구요?
// post란 이름으로 라우터 따로 만들어둔거 있는데
// 걔가 해주는 라우팅에 맞춰서 보여드림 ㅅㄱ
const posts = require('./posts');
api.use('/posts', posts.routes());
// 라우터 내보내기
module.exports = api;
파일경로 : 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;
2.2) 컨트롤러 파일
라우트 처리 함수만 모아 놓은 파일을 컨트롤러 파일이라고 한다. 이게 왜 필요한가 싶을텐데 아래 예시를 보면, '/about/name' 경로로 GET요청이 왔을 때 처리해야 할 코드가 100만줄쯤 된다고 치자. 이 경우 한 파일 안에서 작성하면 지옥을 볼 수 있을 것이다.
router.get('/about/:name?', ctx => {
// 나는 이것도 해야되고
// 저것도 해야되고
// 한 백만줄쯤 처리해야됨
});
그래서 보통 xxxx.ctrl.js 파일로 컨트롤러를 만들고, exports.이름 = ... 형식으로 함수를 내보내준다. 그렇게 만들어두면 나중에 아래 형식으로 불러와 사용할 수 있게된다.
- const 모듈이름 = require('파일이름');
- 모듈이름.이름();
파일경로 : src/api/posts/posts.ctrl.js
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;
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);
// 포스트가 없다면 error 반환
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;
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];
};
// 포스트 수정(특정 필드 변경)
// PATCH /api/posts/:id
// { title, body }
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];
}
'책 > 리액트를 다루는 기술' 카테고리의 다른 글
[21장] 타입스크립트 in React (Redux) (0) | 2022.04.16 |
---|---|
[21장] 타입스크립트 in React (Component, State) (0) | 2022.04.16 |
[20장] 서버 사이드 렌더링 (0) | 2022.04.13 |
[19장] 코드 스플리팅 (0) | 2022.04.12 |
[18장] 리덕스 미들웨어(redux-saga) (0) | 2022.04.12 |