본문 바로가기

개발/Tutorial Project

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

시작하기 전에

 

이 글은 MEVN Stack이라고 불리는 MongoDB, Express.js, Vue.js, Node.js를 이용해 SPA로 동작하는 To-do Application을 만드는 튜토리얼이다.

 

튜토리얼은 총 4개의 Part로 구성될 예정이며, Part 1에서는 구현에 앞서 Express.js와 Vue.js를 이용한 통합 개발 환경을 만든다.

 

작성자 환경 버전

 

Node 10.15.3

Express 4.17.1

Vue 2.9.6

Mongo 4.0.2

 

 

 

frontend 폴더 생성하기

 

vue-cli 명령어를 사용하면 간단히 Vue 개발 환경을 구축할 수 있다.

 

$ npm i vue-cli -g // vue-cli가 설치되어 있지 않은 분들은 해당 명령어를 통해 설치해주세요.
$ vue init webpack frontend
vue init 명령어에서는 webpack, webpack-simple 등 6개의 템플릿 옵션을 사용할 수 있다.

 

명령어를 입력 후 나오는 옵션은 다음과 같이 체크하자.

 

 

이제 frontend 폴더로 이동하여 npm run dev 명령어를 통해 서버가 정상적으로 작동하는지 확인해보자.

 

다음과 같은 화면이 나오면 정상적으로 Vue 프로젝트를 생성하고, 동작시킨 것이다.

 

vue-cli로 설치한 프로젝트는 기본 포트가 8080번으로 설정되어 있다.

 

젠킨스와 같이 기존에 8080 포트를 사용하는 프로그램이 있는 경우 frontend/config/index.js의 dev를 다음과 같이 수정하는 것을 추천한다. 

(따로 포트를 변경해주지 않더라도 자동적으로 다음 포트에서 실행이 되긴 한다. 하지만 예외를 방지하기 위해 두 개의 프로그램이 같은 포트를 사용할 경우 서로 다른 포트를 사용하도록 설정하는 것을 권장한다.)

 

dev: {
  ...
  port: 8000,
  ...
}

 

 

컴포넌트 생성

 

현재 루트 페이지에 나타나는 화면은 HelloWorld.vue라는 vue-cli가 생성해준 컴포넌트다.

 

지금부터 루트 페이지에 HelloWorld.vue가 아닌 직접 생성한 컴포넌트가 동작하도록 파일을 생성해보자.

 

src/components에 있는 HelloWorld.vue를 삭제하고 Home.vue 파일을 생성한다.

 

그리고 Home.vue를 다음과 같이 작성한다.

 

<template>
  <h1>메인 페이지입니다.</h1>
</template>

<script>
export default {
  name: 'Home'
}
</script>

<style scoped>
</style>

 

src/router의 index.js에도 다음과 같이 방금 생성한 컴포넌트를 등록한다.

 

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }
  ]
})

 

포트를 변경했기 때문에 서버를 다시 시작해준다. 그리고 브라우저를 확인해보면 다음과 같이 방금 생성한 홈 컴포넌트가 나타나는 것을 확인할 수 있다. (포트 변경 등 특별한 경우가 아니라면 dev 모드에서는 저장한 결과를 바로 반영해준다.)

 

backend 폴더 생성하기

 

이번에는 backend 폴더를 생성하고 구성해보자.

 

Express도 vue-cli처럼 express-generator를 이용해 스캐폴딩을 구성할 수 있지만 직접 만들어보자.

 

 

backend 폴더를 생성한 후 다음과 같이 npm init을 하여 package.json을 생성하고, express를 설치한다.

 

$ npm init
$ npm i express --s

 

그리고 app.js를 생성한 후 다음 코드를 작성하자.

 

const express = require('express');

const app = express();

let port = 3000;

app.listen(port, () => {
  console.log(`서버가 ${port}에서 동작중입니다.`);
});

 

backend 폴더에서 node app.js 명령어를 통해 서버를 실행한 후 3000번 포트로 접속하면 다음과 같이 "Cannot GET /" 이라는 화면과 함께 express가 정상적으로 동작하는 것을 확인할 수 있다.

("Cannot Get /"이 출력되는 이유는 아직 우리가 아무런 라우터도 작성하지 않았기 때문이다.)

 

Vue.js와 Express.js 연동하기

 

지금까지 우리는 frontend와 backend 두 개의 폴더를 만들었다.

 

그리고 현재 Vue는 8000번 포트, Express는 3000번 포트에서 동작하고 있다.

 

즉, 두 개의 서버가 각각 돌아가고 있는데 우리는 앞으로 Webpack을 통해 Vue 프로젝트를 번들링하여 Express가 존재하는 backend 폴더에 전달할 것이다. 그러면 8000번 포트를 사용하지 않고, Express가 동작하는 3000번 포트만을 사용하여 서버를 동작시킬 수 있다.

 

 

Vue 프로젝트를 backend 폴더로 번들링하기 위해서 frontend/config/index.js 파일의 build 부분을 다음과 같이 수정하자.

 

build: {
  // Template for index.html
  index: path.resolve(__dirname, '../../backend/public/index.html'), // 수정

  // Paths
  assetsRoot: path.resolve(__dirname, '../../backend/public'), // 수정
  assetsSubDirectory: 'static',
  assetsPublicPath: '/',
  ..
}

 

기존의 코드와 비교하면 변경된 코드가 가리키는 경로(path)가 backend 폴더의 public 폴더로 바뀐 것을 확인할 수 있다.

 

이러한 흐름은 webpack으로 번들링 된 Vue 프로젝트가 backend/public 폴더에 저장되고, 앞으로 Express를 통해 들어오는 요청은 Express 라우터를 타고 빌드된 Vue 라우터로 전달되게 된다.

 

SPA는 SSR과 달리 요청마다 새로운 페이지를 렌더링하지 않는다. 즉, 여러 화면을 하나의 페이지에서 보여주고 있지만 이는 매번 새로운 HTML을 보여주는 것이 아닌 내부적인 라우터를 이용하여 요청 URI에 따라 브라우저에서 DOM을 변경하는 것이다.

 

이것이 앞서 우리가 vue-router를 사용하는 이유이다.

 

우리가 만들 웹은 초기에 번들링 된 index.html 하나만을 렌더링한다. 지금은 여기까지만 알고 있어도 좋다.

 

 

 

이제 Vue 프로젝트가 번들링 될 위치를 작성했으니, backend 폴더로 돌아와 public 폴더를 생성해주자.

 

그리고 Express 서버를 중지한 후 frontend 폴더에서 npm run build 명령어를 입력하면 다음과 같이 public 폴더에 번들링 된 폴더와 파일들이 생성된 것을 확인할 수 있다.

 

 

이제 우리는 번들링 된 public 폴더를 통해 Vue 프로젝트의 8000번 포트를 이용하지 않고, Express가 동작하는 3000번 포트만으로 Vue와 Express를 동작시킬 준비가 되었다. 

 

 

지금부터 본격적인 연동을 시작해보자.

 

backend 폴더에 routes 폴더를 생성하고, index.js 파일을 생성한 후 다음과 같이 코드를 작성한다.

 

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

router.get('/', (req, res, next) =>{
  res.sendFile(path.join(__dirname, '../public', 'index.html'));
});

module.exports = router;

 

app.js에도 다음 코드를 추가해준다.

 

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

app.use(express.static('public'));

app.use('/', indexRouter);

 

추가로 Vue Router와 Express 연동을 위해 connect-history-api-fallback 모듈을 설치한다.

 

$ npm i connect-history-api-fallback --s
Vue Router는 기본적으로 hash mode를 사용한다.

하지만 우리가 앞서 작성한 라우터 코드를 확인해보면 mode: 'history'를 통해 hash mode가 아닌 history mode를 사용하는 것을 확인할 수 있다. 그리고 우린 Express 서버에서 history API를 사용하기 위해 해당 모듈을 설치하는 것이다.

Vue Router가 제공하는 라우팅 모드에 대한 자세한 설명은 
https://router.vuejs.org/kr/guide/essentials/history-mode.html 를 참고하길 바란다.

 

그리고 app.js에 추가한 모듈을 등록해주자.

 

const history = require('connect-history-api-fallback');

app.use(history());

 

이제 backend에서 서버를 실행시킨 후 localhost:3000으로 접속하면 해보면 Express 서버에서 요청을 보냈지만 Vue Router에 의해 홈 컴포넌트에서 작성했던 화면이 나타나는 것을 확인할 수 있다.

 

 

마무리

 

지금까지 Vue와 Express를 연동하여 하나의 서버로 동작시키는 환경을 구축했다.

 

당장은 이해가 잘 되지 않을 수도 있다. 지금은 단순히 우리가 만든 웹이 Express 서버만 떠있는 상태에서 Express로 요청을 보내면 번들링 된 Vue 프로젝트에 의해서 내부적으로 Vue Router가 동작한다는 것만 기억하자.

 

뒤에 이어질 Part 2에서는 Express와 MongoDB를 이용해 REST API 서버를 구성하는 과정을 다룰 예정이다.

 

 

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


 

 

 

 

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

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

 

Mail: twysg@likelion.org

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