웹 서버 프레임워크

Node.js의 웹 서버 프레임워크인 Express에 대해서 알아보고, Express를 기반으로 Flickr 갤러리를 리팩토링합니다.
웹 서버, Express.js


Express.js

Express.jsExpress.js

Express는 Node.js 기반의 아주 작은 웹 서버 프레임워크입니다. Node.js의 http 내장 모듈을 기반으로 웹 서버 개발에 적합하도록 추상화된 Application (서버), Router, Request, Response 객체들을 제공합니다. 라우팅, 미들웨어, 템플릿 등의 기능을 제공하며 다른 패키지들을 결합해 확장성있게 이용 할 수 있습니다. 챕터를 진행하시기 전에 Express 안내서 및 API 문서를 어느 정도 공부하시길 바랍니다.

express 및 ejs 템플릿 엔진, nodemon 설치

~$ npm install nodemon -g

~$ mkdir flickr-express
~$ cd flickr-express
~/flickr-express$ npm init
~/flickr-express$ npm install express ejs --save

Application 객체

var express = require('express');
var app = express();

app.locals.forTemplate = "Hello World!";
app.locals.forTemplate2 = [1,2,3,4,5];
app.set('views', 'templates');
app.set('view engine', 'ejs');
console.log(app.locals);

app.listen(8000);

라우팅과 미들웨어, Router 객체
// 라우팅 .post, .put, .delete, .all, ..
app.get('/', (req,res) => {
    res.send("Hello!");
});

app.get('/hello/:name', (req,res) => {
    res.send("Hello "+req.params.name);
});

// 미들웨어 (어플리케이션)
app.use((req, res, next) => {
        console.log('Time: %d', Date.now());
    res.locals.forTemplate3 = "Hello Again!";

    next();
});

app.use('/bye', (req, res, next) => {
    res.send('Bye');
});

// 미들웨어 (서드파티)
app.use(express.static('assets'));

// 미들웨어 (라우터 객체)
var router = express.Router();
router.use((req,res,next) => { ... });
router.get('/some/path', (req,res) => { ... });
app.use('/basePath', router);

// 미들웨어 (라우트 핸들러)
app.get('/profile/:user_id', (req,res,next) => {
    if (req.params.user_id == 0)
        next('Not Logined'); // 오류 처리 미들웨어로
    else
        next(); // 다음 핸들러로
}, (req,res) => {
    res.send("Hello "+req.params.user_id);
});

// 미들웨어 (오류 처리)
app.use((err,req,res,next) => {
    console.error(err);
    res.status(500).end();
});

Request, Response 객체

req.cookies
req.headers['Content-Type']
req.params
req.query
req.ip
req.method
req.get('Content-Type');

res.status(404);
res.redirect('/...');
res.set('Content-Type', '...');
res.set({
    'Content-Type': '...',
    '...': '...'
});

res.end();
res.send('....');
res.render('viewFileName', {
  title: 'aaa',
  list: ['aa','bb'],
  obj: {a:1,b:2}
});
res.json({a: 1,b: 2});

EJS 템플릿

EJS(Embedded JavaScript)는 템플릿 엔진의 일종입니다. .ejs 파일에 HTML과 JavaScript (Server-side)를 섞어 쓰고, EJS 엔진을 통해 HTML 문서를 생성합니다.

EJS 공식 페이지

Express에서 EJS 이용하기

app.set('view engine', 'ejs');
app.set('views', 'templates');

res.render('index', { // ./templates/index.ejs를 렌더링
  title: "Hello World!",
  users: ["user1","user2"]
});

./templates/index.ejs

<html>
<body>
<%
// Server-side JavaScript (Node.js)
if ( .. ) {
    // window... 존재하지 않습니다.
    // document... 도 존재하지 않습니다.
}
console.log("Hi!"); // 웹 브라우저가 아니라 셸에 출력됩니다.
%>

<!-- title의 값을 문서에 삽입 -->
<h1><%=title%></h1>

<%
// 제어 로직을 통해 문서에 태그를 삽입
for (var i=0; i < 10; i++) {
%>
<span><%=i%></span>
<% } %>

<% users.forEach(user => { %>
<span><%=user%></span>
<% }); %>

<!-- 아래 script 태그의 javascript는 렌더링시에 단순한 텍스트에 불과합니다. -->
<script>
doc<%='u'%>ment.title = "this is client-side javascript";
</script>
</body>
</html>

반복적인 HTML을 재사용함으로서 유지보수의 효율을 높힐 수 있습니다. 일반적인 웹페이지의 HTML의 구조를 (1) html-head-body-네비게이션/사이드바-(2) 페이지마다 달라지는 내용-(3) 하단부-/body-/html로 본다면 (1)과 (3)은 공통적인 부분이므로 여러 페이지에서 재사용 할 수 있습니다.

./template/header.ejs

<html>
<body>
<h1>My Homepage</h1>
<ul class="nav navbar-nav">
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
</ul>

<div class="contents">
./template/footer.ejs
</div>
<div>© 2016 benzen</div>
</body>
</html>

./template/home.ejs

<% include ./header %>

home contents..

<% include ./footer %>

./template/about.ejs

<% include ./header %>

about contents..

<% include ./footer %>

Flickr 리팩토링

Express를 이용하여 지난 챕터의 Flickr 갤러리를 리팩토링합니다.

스펙

  • 작은 사진을 보여주는 라우팅, 큰 사진을 보여주는 라우팅, 사진 데이터를 JSON으로 주는 라우팅 만들기
  • EJS를 이용해 데이터와 뷰의 완전한 분리
  • header와 footer를 쪼개 재사용하며, header에는 네비게이션을 포함하고, 현재 위치한 페이지를 인식할 수 있게 하이라이팅
  • 404 Not Found를 처리하는 미들웨어 만들기
  • 500 Error를 나타내는 미들웨어 만들기

app.js

const express = require('express');
const app = express();

// 환경 설정
app.set('view engine', 'ejs');
app.set('views', 'templates');

// app.locals와 res.locals
app.locals.title = "Flickr Photos";
app.use((req,res,next)=>{
    res.locals.url = req.url;
    next();
});

// 라우터 적용
app.use(require('./router'));

// 404 처리
app.use((req, res, next) => {
  res.status(404).send('Not Found');
});

// 에러 처리 미들웨어
app.use((err, req, res, next) => {
    console.error(err);
  res.status(500).send('Internal Server Error');
});

// 서버 시작
app.listen(8000);
console.log('Server started at port 8000', new Date);
router.js
const flickr = require('./flickr-api');
const express = require('express');
const router = express.Router();

// 리다이렉션
router.get('/',(req,res)=>{
    res.redirect('/small');
});

// 작은 사진 보여주기
router.get('/small', (req, res) => {
    flickr.getRecentPhotos(photos => {
      res.render('small', {
        photos: photos
      });
    });
});

// 큰 사진 보여주기
router.get('/large', (req, res) => {
    flickr.getRecentPhotos(photos => {
      res.render('large', {
        photos: photos
      });
    });
});

// JSON으로 응답
router.get('/json', (req, res) => {
    flickr.getRecentPhotos(photos => {
      res.json({
        title: req.app.locals.title, // req에 Application 객체를 참조 할 수 있는 app 속성이 있음
        photos: photos
      });
    });
});

module.exports = router;

전체 소스코드

목차
4. 웹 백엔드
5. 데이터베이스
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 04월 11일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.