• 스키마는 컬렉션에 들어가는 문서 내부의 각 필드
  • 모델은 스키마를 사용하여 만드는 인스턴스
    • 데이터베이스에서 실제 작업을 처리하는 함수들을 지니고 있는 객체

schema와 모델 생성

  • schema와 모델에 관련한 코드는 src/models에서 관리
  • 제목, 내용, 태그, 작성일
  • Schemamongoose의 모듈을 사용
  • 아래와 같이 AuthorSchema의 경우 BookSchema의 일부로도 정의가 가능하다.
import mongoose from 'mongoose';

const { Schema } = mongoose;

const AuthorSchema = new Schema({
  name: String,
  email: String,
});

const BookSchema = new Schema({
  title: String,
  description: String,
  authors: [AuthorSchema],
  meta: {
    likes: Number,
  },
  extra: Schema.Types.Mixed,
});

const PostSchema = new Schema({
  title: String,
  body: String,
  tags: [String],
  publishedDate: {
    type: Date,
    default: Date.now,
  },
});

const Post = mongoose.model('Post', PostSchema); // schema name(=posts), schema obj, <custom collection name>
export default Post;
  • mongodb에서는 model의 파라미터 첫번째로 Post를 넘기는데 컬렉션을 생성할때 @+s로 생성
  • 만약 내가 정의하고 싶은 컬렉션의 이름이 있다면 3번째 파라미터로 custom_collection_name을 넘기면 된다.
  • node.js v12 이후부터는 ES Module이 지원
  • ES Module에서 import/export 문법을 사용 가능
  • node --version으로 버전 확인 (현재 버전은 v12.15.0)
  • package.json에 아래와 같이 추가하면 ES Module을 바로 사용
(...)
  "scripts": {
    "start": "node src esm src",
    "start:dev": "nodemon --watch src/ -r esm src/index.js"
  },
  "type": "module"
$ yarn add esm
exports.write = ctx => {
  const { title, body } = ctx.request.body;
  postId += 1;
  const post = { id: postId, title, body };
  posts.push(post);
  ctx.body = post;
}; 
  • export에 에러가 발생할텐데 .eslintrc.json에 아래와 같이 "sourceType": "module"을 추가
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  • 모든 .js의 파일에서 reuiqreimport/export로 변경
import Router from 'koa-router';
import posts from './posts';

const api = new Router();

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

export default api;

mongoose

  • mongoose는 node.js 환경에서 사용하는 라이브러리
  • ODM (Object Data Modeling)

dotenv

  • 환경변수들을 파일에 넣고 사용할 수 있게 하는 개발 도구
  • mongoose를 사용해서 id/password를 스크립트에 작성하지 않고 환경병수로 관리.env
yarn add mongoose dotenv

ROOT에 .env 파일 생성

PORT=4000
MONGO_URI=mongodb://localhost:27017/<database>

src/index.js

require('dotenv').config();
(...)
const { PORT } = process.env;
(...)
const port = PORT || 4000;
app.listen(port, () => {
  console.log('Listening to port %s', port);
});
  • 만약 nodedemon으로 자동 재시작을 하고 있다면, .env의 파일을 변경해도 재시작을 하지 않으니 수동으로 재시작

mongoose로 서버와 데이터베이스 연결


(...) 
const { PORT, MONGO_URI } = process.env;
(...)

 mongoose
  .connect(MONGO_URI, { useNewUrlParser: true, useFindAndModify: false })
  .then(() => {
    console.log('Connected to MongoDB');
  })
  .catch(e => {
    console.error(e);
  });
  • 화면에 Connected to MongoDB가 출력되면 연결 완료.
  • 각 라우터에서 처리해야하는 함수 코드가 길면 유지보수가 어려우니
  • 라우트에서 처리하는 함수들만 모아서 컨트롤러(controller)를 만들자
# yarn add koa-bodyparser

src/api/posts/posts.ctrl.js

let postId = 1; // init

// posts data

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

/* 포스트 작성
POST /api/posts
{ title, body }
*/
exports.write = ctx => {
  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; // default datatype: string
  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;
  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
};

/* 포스트 수정 (교체)
PUT /api/posts/:id
{ title, body }
*/
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];
};

/* 포스트 수정 (특정 필드 변경)
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];
};

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; 
  • 여러개의 Router를 생성하게 될텐데, 이렇게 되면 유지보수가 이후에는 어려워질수 있으니 router 모듈화를 진행
  • src/api/index.js 에 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();

// 라우터 설정
router.use('/api', api.routes()); // add api router

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

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

src/api/index.js

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

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

module.exports = api;
  • src/api/index.js를 생성하고, src/index.jsrouter.use를 통해 router를 등록해주면 된다.
  • 이때 주의해야할점은 src/api/index.js에서 반드시 exports를 해줘야 한다.

posts API 생성하기

  • src/api/posts/index.js에 아래와 같이 posts에 관련된 api의 목록들을 정의한다.
  • 정의한 route를 src/api/index.js에 적용을 해주면 된다.

src/api/index.js

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

const api = new Router();

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

module.exports = api;

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;
  • 위 같이 정의하고 http://localhost:4000/api/posts/12을 호출하면 아래와 같은 결과가 나온다.

$ yarn add koa-router
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 instance에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());

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

Router Parameter, Query

  • /about/:name의 형식으로 :을 사용하여 라우트 경로 설정
  • ctx.params의 객체에서 조회
  • /posts/?id=10 처럼 조회하면 ctx.query에서 조회
    • 문자열 형태는 ctx.querystring
router.get('/about/:name?', ctx => {
  const { name } = ctx.params;
  ctx.body = name ? `${name}의 소개` : '소개';
});

router.get('/posts', ctx => {
  const { id } = ctx.query;
  ctx.body = id ? `포스트 #${id}` : `포스트 없어`;
});
  • http://localhost:4000/about/icecream
  • http://localhost:4000/posts?id=10

코드를 수정하고 반영하기 위해서는 서버를 재시작을 해야하는데 nodemon을 이용하면 코드를 변경할때마다 자동으로 서버를 재시작을 해준다. yarn을 통해 설치가 가능하다

$ yarn add --dev nodemon
  • 패키지를 설치한 이후에는 package.json에 아래와 같이 추가
{
  (...)
  ,
  "scripts": {
    "start": "node src",
    "start:dev": "nodemon --watch src/ src/index.js"
  }
}

서버시작하기

$ yarn start # 재시작이 필요 없을때
$ yarn start:dev # 코드 변경시 재시작 하도록

적용전

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

적용후

app.use(async (ctx, next) => {
  console.log(ctx);
  console.log(1);
  await next();
  console.log('END');
});

+ Recent posts