이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.
이 게시물의 소스코드는 Tour of Heroes / HTTP에서 이어집니다.
tour-of-heroes.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.
tour-of-heroes.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 tour-of-heroes.git 을 clone 합니다.
- Github에서 소스코드 보기: https://github.com/a-mean-blogger/tour-of-heroes/tree/5febff197d61a2970ca102b2534f6d70aa21706d
Angular 2/Tour of Heroes에서 Angular로 사이트를 만드는 방법을, Node JS API/기본 REST API 만들기에서 API를 만드는 방법을 알아보았습니다. 이처럼 두 개의 분리된 프로젝트형식은 프론트 엔드 개발자와 백엔드 개발자가 나누어져 있을때 유용한 방식입니다. 만약 혼자서 전체를 개발하는 경우에는 두개로 나누어져있으면 오히려 번거로울 수도 있습니다.
이번 포스팅에서는 Angular 2/Tour of Heroes의 코드를 베이스로 하고 여기에 Node JS API/기본 REST API 만들기의 코드를 더해서 하나의 서버로 Angular 사이트와 Node JS REST API를 실행하는 방법에 대해 알아보겠습니다
이번 강좌는 단편강좌/Angular CLI 사이트 Heroku(헤로쿠)로 인터넷에 올리기와도 연관성이 많습니다. Angular CLI 사이트를 헤로쿠에 올리려면 서버를 만들어 주어야 하기 때문이죠. 위 강좌와 중복되는 부분도 있지만 포스팅의 독립성을 위해서 그 부분들도 이 포스팅에서 다시 한번 설명하였습니다.
Angular CLI 프로젝트는 개발(development) 단계에서 ng server
명령어를 통해 사이트 실행합니다. 이 명령어는 컴퓨터의 임의의 위치에 Angular 파일들을 생성하고 임시 서버(기본설정: localhost:4200)를 만들어 그 파일들을 접근가능하게 하고, 코드에 수정이 있으면 실시간으로 서버의 파일을 수정하여 사이트에 반영하는 역할을 합니다. 사이트를 운영(production) 단계에서는 사이트의 성능을 떨어트리기 때문에 사용하지 말아야 합니다.
Angular CLI 프로젝트는 아래 명령어를 통해서 production용 사이트 파일들을 얻을 수 있습니다.
$ ng build --prod
이 명령어를 실행하면 프로젝트_폴더/dist 위치에 index.html을 비롯한 angular 관련 static 파일들이 생성됩니다. Angular 사이트는 static 파일들로만 구성되어 있고 그 파일들을 서버에 올려 index.html에 접근가능하게 하면 Angular 사이트가 바로 작동합니다.
Node JS API/기본 REST API 만들기 이미 Node JS 서버코드가 있으므로 Angular 2/Tour of Heroes 프로젝트로 이 코드를 옮기고 프로젝트_폴더/dist의 index.html를 접근하게 하면 두개의 프로젝트를 하나의 서버에서 작동하게 할 수 있습니다.
물론 위 이야기는 production 단계에서의 이야기입니다. 개발중에는 Angular 코드가 변화면 그때그때 새로 사이트를 업데이트해주는 기능이 있는 것이 편합니다. 즉 개발중에는 두개의 서버로, production 단계에서는 하나의 서버로 만드는 것이 이 포스팅의 첫번째 목표입니다.
두번째 목표는 Angular 사이트가 현재 개발환경에 맞게 각각의 주소로 API 요청을 하게 하는 것입니다.
현재 Angular 2/Tour of Heroes 코드는 서버 코드에서 필요한 package들이 설치되어 있지 않습니다. 아래 명령어를 입력하여 서버 코드에서 사용되는 pakcage들을 설치합시다
$ npm install express mongoose body-parser --save
두 개의 프로젝트가 합쳐지면서 파일들이 너무 많아져서 새로추가된 파일(초록색)과 수정된 파일(주황색)만 표시했습니다.
Angular 2/Tour of Heroes 강의에서는 살펴보지 않았던 environments 폴더의 파일들이 수정됩니다. 이 environments의 파일들로 부터 Angular의 실행환경(development, production 등)에 따라 다른 변수값을 가져올 수 있습니다.
server 폴더의 다른 파일들은 Node JS API/기본 REST API 만들기의 파일을 그대로 가져왔기 때문에 따로 살펴보지 않고 server.js파일만 살펴보겠습니다.
// server/server.js var express = require('express'); var app = express(); var path = require('path'); var mongoose = require('mongoose'); var bodyParser = require('body-parser'); // Database mongoose.Promise = global.Promise; mongoose.connect(process.env.MONGO_DB, {useMongoClient: true}); var db = mongoose.connection; db.once('open', function () { console.log('DB connected!'); }); db.on('error', function (err) { console.log('DB ERROR:', err); }); // Middlewares app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.header('Access-Control-Allow-Headers', 'content-type'); next(); }); // API app.use('/api/heroes', require('./api/heroes')); //* // Angular app.use(express.static(path.resolve(__dirname, '../dist'))); //1 app.get('*', function (req, res) { //2 var indexFile = path.resolve(__dirname,'../dist/index.html'); res.sendFile(indexFile); }); // Server var port = 3000; app.listen(port, function(){ console.log('listening on port:' + port); });
server.js파일도 // Angular 부분을 제외하면 기존의 파일과 동일합니다.
1. 프로젝트_폴더/dist를 static 파일 위치로 만듭니다. 그 위치에 angular 파일들이 생성되기 때문입니다.
2. 모든 요청을 Angular의 index.html파일로 전달합니다. API에 관련된 요청은 //*에서 먼저 처리되기 때문에 API 요청을 제외한 모든 route 이 Angular로 전달됩니다. 그렇기 때문에 이 코드는 API route 코드(//*) 밑에 있어야 합니다.
서버에 관련된 코드는 server폴더 안에 있는 것들 뿐이고 다음은 Angular에 관련된 코드들입니다.
// src/app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { DashboardComponent } from './dashboard.component'; import { HeroDetailComponent } from './hero-detail.component'; import { HeroesComponent } from './heroes.component'; import { HeroService } from './hero.service'; import { HeroSearchComponent } from './hero-search.component'; import { AppRoutingModule } from './app-routing.module'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, AppRoutingModule ], declarations: [ AppComponent, DashboardComponent, HeroDetailComponent, HeroesComponent, HeroSearchComponent ], providers: [ HeroService ], bootstrap: [ AppComponent ] }) export class AppModule { }
app. module.ts에서 추가된 부분은 없고, 이전 강의에서 사용됬던 (In Memory Web Api Module) 부분을 삭제하였습니다. 진짜 API를 사용하기 때문에 더이상 가짜 API를 사용할 필요가 없죠.
environments 폴더의 파일들을 살펴봅시다.
// src/environments/environment.prod.ts export const environment = { production: true, apiBaseUrl:"api", }; // src/environments/environment.ts export const environment = { production: false, apiBaseUrl:"http://localhost:3000/api", };
ng build --prod를 실행하면 Angular 사이트는 environment.prod.ts의 값을, ng server를 실행하면 environment.ts의 값을 읽어오게 됩니다. 개발환경에서는 서버가 두개이므로 API의 절대주소를, 운영환경에서는 서버가 하나이므로 API의 상대주소를 적습니다. 개발환경에 따라 Angular사이트에 달라지는 값이 있다면 여기에 적어주면 됩니다.
// src/app/hero.service.ts import { environment } from '../environments/environment'; //1 import { Injectable } from '@angular/core'; import { Headers, Http } from '@angular/http'; //... @Injectable() export class HeroService { private apiBaseUrl = environment.apiBaseUrl; //2 private headers = new Headers({'Content-Type': 'application/json'}); private heroesUrl = `${this.apiBaseUrl}/heroes`; // URL to web api //3 constructor(private http: Http) { } // ... }
1. environment 오브젝트를 가져옵니다. Angular 실행환경(production, development)에 따라 사이트가 알아서 environment.ts 혹은 environment.prod.ts에서 값을 읽어옵니다.
2. apiBaseUrl을 가져옵니다.
3. apiBaseUrl을 이용해서 heroes API의 url을 만듭니다.
// src/app/hero-search.service.ts import { environment } from '../environments/environment'; //1 import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; // ... @Injectable() export class HeroSearchService { private apiBaseUrl = environment.apiBaseUrl; //2 constructor(private http: Http) {} search(term: string): Observable<Hero[]> { return this.http .get(`${this.apiBaseUrl}/heroes/?name=${term}`) //3 .map(response => response.json().data as Hero[]); } }
search service 에서도 비슷한 방법으로 처리됩니다. 형태가 동일하지 않은건 원래 tour of heroes 예제가 그렇습니다.
이것으로 Angular 사이트에 관련된 코드도 모두 수정하였습니다. 마지막으로 package.json을 수정합시다.
// package.json { "name": "tour-of-heroes", "version": "0.0.0", "license": "MIT", "scripts": { "ng": "ng", "start": "node ./server/server.js", //1 "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "local": "ng serve | nodemon" //2 }, // ... }
1. production 환경에서는 npm start 명령을 통해 사이트를 실행할 수 있습니다. npm start는 package.json의 start 스크립트를 실행시킵니다. 이 부분을 node 명령어로 서버를 실행시키게 합니다. production 환경에서 ng server로 Angular 사이트를 실행시키지 않은 것과 같은 이유로 production 환경에서는 nodemon으로 사이트를 실행시키지 말아야 합니다.
2. local script는 사용자 정의 스크립트입니다(즉 이름을 원하는대로 바꿔도 됩니다) 사용자 정의 스크립트는
$ npm run 스크립트_이름
를 통해 실행시킬 수 있습니다. nodemon과 ng server를 동시에 실행하게 됩니다.
아래 명령어를 입력합니다.
$ npm run local
local script에 의해 nodemon과 ng server가 동시에 실행되며 사이트는 ng server에 의해서 localhost:4200 에서 접근할 수 있습니다. 서버 파일에 수정이 있는 경우에는 nodemon에 의해 REST API 서버가 재실행되고, Angular 파일에 수정이 있는 경우에는 ng server에 의해 Angular사이트가 재조립됩니다.
아래 명령어를 입력하여 Angular 사이트 static 파일들을 생성합니다.
ng build --prod
Angular 사이트 static 파일들이 생성되고 나면 아래 명령어로 서버를 실행시킵니다.
$ npm start
프로젝트를 하나로 합치면 헤로쿠(heroku)를 통해 프로젝트를 인터넷에 올리기도 훨씬 간편합니다. 다만 프로젝트가 node js와 Angular를 모두 가지고 있으므로 단편강좌/Node js 사이트 Heroku(헤로쿠)로 인터넷에 올리기와 단편강좌/Angular CLI 사이트 Heroku(헤로쿠)로 인터넷에 올리기의 변화가 모두 적용되어야 합니다. 설명은 따로 하지 않고 소스코드는 여기에서 볼 수 있습니다. 프로젝트 폴더에서 아래 명령어를 사용하면 헤로쿠용 코드로 변환됩니다만 기존의 코드들이 모두 사라지므로 주의하세요
$ git reset --hard 5573fd4
댓글
이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기