본문 바로가기

개발/Tutorial Project

Express.js와 Vue.js로 만드는 SPA(Single Page Application) Part 2.

이전 Part 1에서 우리는 Express와 Vue를 연동한 개발 환경을 구축했다.

 

Part 2에서는 Express와 MongoDB를 이용해 REST API 서버를 구축하는 과정을 다룬다.

 

 

 

Mongoose 설치

 

Mongoose란 MongoDB ODM(Object Document Mapping) 중 하나이다.

 

Mongoose를 이용하면 Object와 Document를 매칭할 수 있다. 즉, Mongoose는 Document를 DB에서 조회할 때 자바스크립트 객체로 바꿔주는 역할을 한다.

 

다음 명령어를 이용해 backend에 mongoose를 설치하자.

 

$ npm i mongoose --s

 

 

MVC 구축

 

Node.js를 사용하면 Ruby On Rails나 Django와 달리 원하는 형태의 디자인 패턴을 구축하는데 수월하다.

 

그리고 우리는 대표적인 디자인 패턴인 MVC를 이용해 우리의 웹 애플리케이션을 구성할 것이다.

 

먼저 backend 폴더에 controller, model 두 개의 폴더를 생성한다.

 

그리고 model 폴더에 index.js와 todo.js를 생성하고 다음과 같이 코드를 작성하자.

 

// index.js

const mongoose = require("mongoose");

module.exports = () => {
  const connect = () => {
    if (process.env.NODE_ENV != "production") {
      mongoose.set("debug", true);
    }
    mongoose.connect(
      process.env.MONGO_URI,
      { user: process.env.MONGO_USER, pass: process.env.MONGO_PASS },
      error => {
        if (error) {
          console.log("몽고디비 연결에 실패했습니다.", error);
        } else {
          console.log("몽고디비 연결에 성공했습니다.");
        }
      }
    );
  };
  connect();
  mongoose.connection.on("error", error => {
    console.error("몽고디비 연결이 끊어졌습니다.", error);
  });
  mongoose.connection.on("dicsonnected", error => {
    console.error("몽고디비에 재연결합니다.");
    connect();
  });
};
// todo.js

const mongoose = require('mongoose');

let TodoSchema = new mongoose.Schema({
  // To-do title
  title: {
    type: String,
    required: true
  },
  // To-do create date
  createAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('Todo', TodoSchema);

 

이제 라우터를 작성할 차례이다.

 

이전 Part 1에서는 Root path("/")로 접근 시 routes/index.js에 번들링 된 index.html을 보여주는 코드를 작성했다.

 

우리는 Express를 REST API 서버로 사용할 것이기 때문에 지금부터 To-do Application에 필요한 API만 작성하면 된다.

 

먼저 routes 폴더에 todo.js를 생성하고 다음과 같이 코드를 작성하자.

 

const express = require('express');
const router = express.Router();

const todosController = require('../controller/todosController');

// READ
router.get('/', todosController.index);

// CREATE
router.post('/create', todosController.create);

// Edit
router.get('/edit/:id', todosController.edit);

// UPDATE
router.put('/:id', todosController.update);

// DELETE
router.delete('/:id', todosController.delete);

module.exports = router;

 

이제 Express 서버로 오는 요청은 Express Router에 의해 Controller로 넘어가게 된다.

 

하지만 우리의 컨트롤러는 아직 비어있는 빈 폴더이다.

 

컨트롤러에 todoController.js를 생성하고 다음과 같이 코드를 작성하자.

 

const Todo = require('../model/todo');

exports.index = (req, res, next) => {
  Todo.find({}, (err, todos) => {
    if (err) {
      return next(err);
    }
    res.status(201).json(todos);
  });
};

exports.create = (req, res, next) => {
  let todo = new Todo({
    title: req.body.title,
  });

  todo.save((err) => {
    if (err) {
      return next(err);
    }
    res.status(200).json(todo);
  });
};

exports.edit = (req, res, next) => {
  Todo.findById(req.params.id, (err, todo) => {
    if (err) {
      return next(err);
    }
    res.status(200).json(todo);
  });
};

exports.update = (req, res, next) => {
  Todo.findByIdAndUpdate(req.params.id, { $set: req.body }, (err, todo) => {
    if (err) {
      return next(err);
    }
    res.status(200).json(todo);
  });
};

exports.delete = (req, res, next) => {
  Todo.findByIdAndRemove(req.params.id, (err) => {
    if (err) {
      return next(err);
    }
    res.status(204);
  });
};

 

그리고 이전에 작성한 model/index.js를 확인해보면 process.env를 확인할 수 있다.

 

process.env는 DB에 접근하기 위한 계정 등의 민감한 정보를 환경 변수로 사용한 것이다. 그리고 우리는 Express에서 환경 변수를 사용하기 위해 dotenv라는 모듈을 사용한다.

 

다음 명령어를 통해 dotenv를 설치한다.

 

$ npm i dotenv --save

 

그리고 backend 폴더에 .env를 생성하고 다음과 같이 작성한다.

 

PORT=3000
MONGO_URI="mongodb://localhost/TodoDB"
MONGO_USER = "dev_user"
MONGO_PASS = "dev_pass"

 

위 코드는 Collection의 이름을 TodoDB로 설정하고 MongoDB의 사용자의 아이디와 패스워드를 환경 변수로 등록하는 코드이다.

 

MongoDB의 계정은 생성되어 있는 Admin 또는 일반 사용자 등 원하는대로 수정할 수 있다.

 

이제 모든 준비가 끝나간다.

 

app.js의 코드를 다음과 같이 수정하자.

 

const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const history = require('connect-history-api-fallback');
require('dotenv').config();

const connect = require('./model');

const indexRouter = require('./routes/index');
const todoRouter = require('./routes/todo');

const app = express();

connect(); // DB 실행

app.set('port', process.env.PORT || 3000);

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public')); 

app.use('/api/todos', todoRouter);
app.use(history());
app.use('/', indexRouter);

// catch 404 and forward to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use((err, req, res, next) => {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render("error");
});

app.listen(app.get('port'), () => {
  console.log(`서버가 ${app.get('port')}번 포트에서 정상적으로 작동합니다.`);
});

 

여기서 주의해야 할 점은 connect-history-api-fallback 모듈이 실행되는 위치이다.

 

Part 1에서는 단순히 index.html을 렌더링하는 것만으로도 충분했기 때문에 history의 위치가 상관없었다.

 

하지만 우리는 앞으로 Express를 REST API 서버로서 사용하면서 다양한 GET Request를 사용할 것이기 때문에 history의 위치가 중요하다.

 

그 이유는 미들웨어의 개념이 필요하다. 간단히 설명하면 Express는 위에서 부터 미들웨어가 차례대로 실행된다.

 

그렇기 때문에 우리가 사용하는 todoRouter 뒤에 history가 실행되야 유효하지 않은 Request를 프론트엔드에서 404 Page 등을 보여주는 방법을 통해 처리할 수 있다. (동작 방식이 궁금한 사람들은 history를 todoRouter 위에 작성하고 아래에서 진행할 Postman을 통해 GET 요청을 보내보자. history가 todoRouter 위에서 먼저 실행된다면 todoRouter의 GET Request가 동작하지 않는 것을 확인할 수 있을 것이다.)

 

아직 프론트엔드를 구성하지 않았기 때문에 지금은 어렵게 생각할 것 없이 요청된 경로가 todoRouter를 모두 체크한 후 맞는 경로를 찾지 못할 경우 history 미들웨어가 실행된 후 프론트엔드에서 해당 요청이 404 Page를 나타내는 방식 등으로 처리된다고 생각하자.

 

그렇기 때문에 아래에 작성한 에러 핸들러는 Express에서만 사용되는 것으로 의도적인 에러를 발생시키지 않는 이상 동작하지 않는다.

 

connect-history-api-fallback 모듈에 대한 자세한 설명은 다음 링크를 참고하길 바란다.

https://github.com/bripkens/connect-history-api-fallback

 

 

 

이로써 REST API 구성은 끝났다.

 

지금까지 우리는 Express 서버를 실행할 때 node 명령어를 사용했다. 하지만 node 명령어로 실행한 서버는 실시간으로 코드의 변화를 감지하지 못하고, 작성한 코드와 같이 예외처리를 해주더라도 서버가 죽는 경우가 발생한다.

 

nodemon은 개발 환경에서 주로 사용되는 모듈로 에러가 발생해도 서버가 죽지 않게 해주고, 코드를 저장할 경우 자동으로 서버를 재시작 해주는 모듈이다.

 

우리는 지금부터 개발 환경에서 node 명령어 대신 nodemon을 통해 서버를 실행할 것이다.

 

다음 명령어로 nodemon 모듈을 전역으로 설치한다.

$ npm i nodemon -g

 

그리고 nodemon app.js를 통해 서버를 실행시켜 보자.

 

콘솔을 확인해보면 몽고 디비 서버 연결이 끊어졌다는 에러가 발생할 것이다. 하지만 우리는 nodemon을 사용했기 때문에 에러가 발생해도 서버가 죽지 않는다.

 

이제 에러를 해결해보자. 

 

몽고 디비 서버 연결에 대한 에러가 발생하는 것은 몽고 데몬이 실행되고 있지 않기 때문이다.

 

몽고 데몬이 시작 프로그램에 등록되어 있지 않다면 따로 몽고 데몬을 실행시켜 줘야 한다.

 

새로운 터미널을 열어서 다음 명령어를 통해 몽고 데몬을 실행시켜 주자.

$ mongod

 

그리고 서버를 재시작 해주면 정상적으로 서버와 DB가 연결된 것을 확인할 수 있다.

 

 

 

API 테스트

 

우리가 지금까지 구성한 REST API는 View가 없는 상태이다. 그 이유는 Part 3에서 해당 API를 통해 Vue.js와 함께 프론트엔드를 구성할 것이기 때문이다.

 

그렇기 때문에 우리는 API를 테스트하기 위해서 Postman이라는 프로그램을 사용한다.

 

Postman은 테스트, 문서화 및 API 개발에 사용되는 HTTP 클라이언트다. 그리고 지금부터 Postman을 사용하여 구현한 엔드 포인트를 테스트 할 것이다. 엔드포인트란 같은 리소스(URI)들에 대해서 HTTP 메서드에 따라 요청들을 구분해주는 것이다.

 

예를 들어 우리가 작성한 API 중 /api/todos/:id는 PUT 요청이 발생하면 라우터에서 todosController.update가 실행되지만 DELETE 요청이 발생하면 todosController.delete가 실행된다.

 

Postman은 두 가지 방법으로 설치할 수 있다.

1. 직접 프로그램 설치 (https://www.getpostman.com)

2. 구글 익스텐션 설치 (https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=ko)

 

설치가 끝났다면 To-do를 생성해보자.

 

다음과 같이 localhost 뒤에 작성한 API URI를 작성한 후 Body 탭에서 x-www-form-urlencoded를 체크하고 title과 value를 작성한다.

그리고 왼쪽 탭에서 POST를 선택하고 Send 버튼을 클릭하면 다음과 같이 json 형태의 응답을 받을 수 있다.

 

 

 

 

마무리

 

지금까지 Express와 MongoDB를 이용해 REST API 서버를 구성했다.

 

그리고 작성한 API를 Postman이라는 HTTP 클라이언트 프로그램을 통해 테스트해봤다.

 

우리는 Create 이외에 4개의 API가 더 존재한다. 해당 API에 대한 테스트는 직접 진행해보자.

 

뒤에 이어질 Part 3에서는 지금까지 작성한 API를 통해 본격적으로 Vue를 이용하여 SPA로 동작하는 Web을 만들 예정이다.

 

 

 

> Part 1. Express.js와 Vue.js로 만드는 SPA(Single Page Application) 보러가기

 


 

 

 

 

안녕하세요. 평범한 대학생 개발자 yorr입니다.

포스팅을 읽고 궁금한 점 또는 문의가 있으신 분은 메일 또는 댓글을 남겨주세요.

 

Mail: twysg@likelion.org

Github: https://github.com/sangyeol-kim