Node.js(express) - cookie-parser

쿠키란 사용자의 컴퓨터에 웹 사이트로부터 저장된 작은 데이터 조각입니다. 이 데이터를 통하여 사용자의 정보 저장, 사이트 내의 움직임 추적, 중단 시점 체크 등 다양한 기능을 제공할 수 있습니다. 쿠키는 일반적으로 브라우저 하위의 폴더로 저장되게 됩니다. 지금 제가 사용하는 맥의 크롬을 기준으로는 /Users/{username}/Library/Application Support/Google/Chrome/Default 폴더의 Cookies라는 파일이 아닐까 합니다. (파일 내부 내용을 봐서는 맞는 것 같은데 정보의 팩트를 체크할 수 가 없네요 ㅠ) 이 파일의 내부를 살펴보면 각 쿠키당 서버의 정보가 있는걸 봐서는 특정 서버에서 만들어진 쿠키는 그 서버에 종속된다고 생각할 수 있겠습니다. 본격적으로 Node.js에서 쿠키를 사용하는 방법을 알아보겠습니다.

Directory

  • project
    • app.js
    • package.json

설치

1
2
3
4
5
npm init
.
.
.
npm i --save express cookie-parser

cookie-parser와 express를 설치합니다. parser라는 이름에서 예상하셨을 수 있겠지만 기존에 배웠던 body-parser와 사용법이 거의 비슷합니다.

예제

  • app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');
 
app.use(cookieParser());
 
app.get('/toggleChk', (req, res) => {
res.cookie('checked', req.cookies.checked ? '' : 'checked'); // set cookie
res.redirect('/');
});
app.get('/', (req, res) => {
res.send(`
<input type="checkbox" ${req.cookies.checked}>
<a href="/toggleChk">쿠키 토글</a>
`);
});
 
app.listen(3000);

cookie-parser 불러와서 미들웨어로 사용하겠다고 선언만 해주시면 req.cookies로 접근이 가능해집니다. 이제 app.js를 실행하고 localhost:3000에 접속하면 체크박스와 버튼(링크)하나가 보입니다. 이 체크박스는 checked라는 이름의 쿠키 값에 따라 체크가 되어있을수도 아닐수도 있습니다. 처음 실행하였다면 몇번을 새로고침 하여도 체크가 되어있지 않을 것 입니다. 이제 쿠키 토글버튼을 클릭해봅시다. 체크가 되어있는 것을 볼 수 있습니다. 이 버튼(링크)은 checked라는 쿠키를 설정하고 루트경로로 이동시켜주는 요청메서드 였습니다. 이제는 몇번을 새로고침하여도 체크박스에는 체크가 되어있을 것 입니다.

참고

cookie-parser

Node.js(express) - body-parser와 multer

Node.js에서 form양식을 submit을 하기위해 사용되는 body-parsermulter 미들웨어에 대해서 간단하게 알아보려 합니다. 각 미들웨어의 용도를 짧게 소개하면 body-parser라우터와 미들웨어 예제때 언급하였듯 요청 바디를 파싱하여서 req.body 객체로 접근할 수 있도록 도와주고, multer는 파일을 업로드를 도와주는 미들웨어 입니다.

Directory

  • project
    • uploads
    • views
      • form.ejs
    • app.js
    • package.json

uploads폴더는 업로드한 파일들이 들어가는 폴더입니다.

설치

1
2
3
4
5
npm init
.
.
.
npm i --save body-parser multer express ejs

예제

body-parser부터 차근차근 진행하도록 하겠습니다.

  • form.ejs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <!DOCTYPE html>
    <html lang="ko">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    *{margin:0; padding: 0;}
    </style>
    </head>
    <body>
    <form action="/userForm" method="post">
    <table>
    <tbody>
    <tr>
    <td>
    <label for="userName">이름</label>
    </td>
    <td>
    <input id="userName" type="text" name="userName">
    </td>
    </tr>
    <tr>
    <td>
    <label for="userAge">나이</label>
    </td>
    <td>
    <input id="userAge" type="text" name="userAge">
    </td>
    </tr>
    <tr>
    <td>
    <label for="userJob">직업</label>
    </td>
    <td>
    <input id="userJob" type="text" name="userJob">
    </td>
    </tr>
    </tbody>
    </table>
    <input type="submit" value="전송">
    </form>
    </body>
    </html>

/userForm 포인트로 post요청을 보내는 form을 생성합니다.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const express = require('express');
    const app = express();
    const bodyParser = require('body-parser');
     
    app.set('views', __dirname + '/views');
    app.set('view engine', 'ejs');
     
    app.use(bodyParser.urlencoded({extended: false}));
    app.use(bodyParser.json()); // 이번예제에서는 사용되지 않습니다...
     
    app.get('/', (req, res) => {
    res.render('form');
    });
    app.post('/userForm', (req, res) => {
    console.log(req.body);
    res.json(req.body);
    });
     
    app.listen(3000);

/userForm으로 post요청이 들어오게되면 req.body객체를 웹페이지에 json형태로 뿌려주도록 하였습니다. 이 상태로 어플리케이션을 실행하여 테스트 해봅니다.

{"userName": "hyeok", "userAge": "20", "userJob": "Front-end developer"} 아마 이러한 형태의 결과를 보실 수 있으시라 생각합니다. 이전 포스팅에서 언급하였듯 form submit이 발생시 기본적으로 Content-Type이 x-www-form-urlencoded로 요청이 들어오는데 이 경우 bodyParser.urlencoded()가 input의 name과 매칭된 req.body 객체를 생성해줍니다. 이제 파일 업로드를 추가해보도록 하겠습니다.

  • form.ejs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <form action="/userForm" enctype="multipart/form-data" method="post">
    .
    .
    .
    <tr>
    <td>
    <label for="userImage">사진</label>
    </td>
    <td>
    <input id="userImage" type="file" name="userImage">
    </td>
    </tr>

input file이 들어간 로우 하나를 테이블에 추가하고, form태그의 enctype을 multipart/form-data로 명시해줍니다. 인코딩 타입을 이렇게 지정해야지만 form태그로 file전송을 할 수 있습니다.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    const express = require('express');
    const app = express();
    const bodyParser = require('body-parser'); // 이 예제에서는 bodyParser가 필요하지 않지만...
    const multer = require('multer');
    const upload = multer({dest: 'uploads/'}); // uploads 폴더에 파일을 저장
     
    app.set('views', __dirname + '/views');
    app.set('view engine', 'ejs');
     
    app.use(bodyParser.urlencoded({extended: false})); // 이번예제에서는 사용되지 않지만...
    app.use(bodyParser.json()); // 이번예제에서는 사용되지 않지만...
     
    app.use(express.static('uploads')); // 업로드된 이미지를 요청하여 표시한다면 넣어줍시다.
     
    app.get('/', (req, res) => {
    res.render('form');
    });
    app.post('/userForm', upload.single('userImage'), (req, res) => {
    console.log(req.body);
    console.log(req.file);
    res.send(`
    body: ${JSON.stringify(req.body)},<br/>
    file: ${JSON.stringify(req.file)}
    `);
    });
     
    app.listen(3000);

multer({dest: 'uploads/'}); 이 함수는 미들웨어를 리턴하며, 옵션으로 들어간 dest의 value인 uploads에(폴더) 파일을 저장하겠다고 설정한 것 입니다. 그리고 리턴된 이 미들웨어는 /userForm에 post요청이 왔을 때 인자로 전해지는 upload.single('userImage') 미들웨어( 인자는 input file의 name값 )로 사용됩니다. 이 단계에서 req.body와 req.file에 접근 가능하도록 도와줍니다.( Content-Type이 multipart/form-data여서 body-parser가 해주지 않아요 ) 이제 어플리케이션을 실행하고 테스트를 진행합니다.

body와 file의 정보가 객체형태로 넘어오고 uploads폴더에 파일이 저장되는것을 볼 수 있지만 이름이 랜덤하고 확장자도 없이 들어오고 있습니다. storage라는 것을 사용하여 파일이 디스크에 저장될때를 컨트롤할 수 있다고 하니 사용해보도록 합시다.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const multer = require('multer');
    const storage = multer.diskStorage({
    destination: (req, file, cb) => {
    cb(null, 'uploads/')
    },
    filename: (req, file, cb) => {
    cb(null, Date.now() + '_' + file.originalname);
    }
    })
    const upload = multer({storage: storage});

큰 변화는 없고 multer의 인자로 dest대신 storage를 설정해줍니다. destination은 파일이 저장될 위치이고, filename은 저장되는 파일의 이름을 설정합니다. 임시방편으로 Date.now()를 사용하였지만 유니크한 값을 만들어주는 모듈들을 설치하여서 사용하는 것을 추천 드립니다. 다시 한번 저장 후 어플리케이션을 실행하면 원본이름을 포함하고 있는 파일이 저장되어 있는 것을 볼 수 있습니다.

맺음

예제에서는 upload.single()을 사용하여 하나의 파일만을 업로드하는 경우를 살펴보았지만, upload.array()나 upload.fields() 등 을 사용하면 여러 파일을 업로드할 수 있습니다. 자세한 내용은 링크에서 확인해 보실 수 있습니다.

Node.js - mysql 연결

mysql 연결하기

데이터베이스에 관한 설명은 최대한 배제하고 node.js에서 mysql을 사용하는 방법 위주로 다루겠습니다. (제가 db를 잘 몰라요 ㅠ..)
예제를 시작하기 앞서 로컬 또는 개인서버에 mysql 설치 후 예제에서 사용하는 table을 만들어야 합니다. 저는 https://www.freemysqlhosting.net/에서 테스트용 mysql 서버를 무료로 호스팅 받았습니다.

1
2
3
4
5
6
7
8
9
10
CREATE TABLE Projects (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(30),
description VARCHAR(255),
author VARCHAR(30),
PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;;
 
INSERT INTO Projects (name, description, author) VALUES ('node example','node js 연습 프로젝트', 'hyeok');
INSERT INTO Projects (name, description, author) VALUES ('javascript example','javascript 연습 프로젝트', 'kh_j');

DB 서버에 접속하여서 Projects라는 테이블과 2개 정도의 테스트용 데이터를 생성하였습니다.
이제 본격적으로 node.js에서 mysql에 접속하는 방법을 살펴보겠습니다.

1
2
3
4
5
npm init
.
.
.
npm i --save express ejs express-ejs-layouts mysql

예제를 진행하기 위한 폴더에서 package.json 파일을 만들고 몇가지 모듈들을 설치합니다.

Directory

  • project
    • config
      • dbconfig.js
    • routes
      • route.js
    • views
      • index.ejs
    • app.js
    • package.json

예제

  • dbconfig.js
    1
    2
    3
    4
    5
    6
    module.exports = {
    host : '<host>',
    user : '<username>',
    password : '<password>',
    database : '<db_name>'
    }

데이터베이스 접속 정보를 모듈화 시켜줍니다. 로컬에 mysql을 설치하셨다면 host는 localhost이며 나머지는 mysql을 설치하면서 설정한 값을 넣어주면 됩니다. 제가 사용한 mysql 호스팅은 가입시 메일로 정보를 보내주더군요.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const express = require('express');
    const app = express();
    const expressLayouts = require('express-ejs-layouts');
    const router = require('./routes/route.js');

    app.set('views',__dirname + '/views');
    app.set('view engine', 'ejs');
     
    app.use(expressLayouts);
    app.use(router);
     
    app.listen(4000);

이전까지 예제와 차이가 없습니다. 간단한 세팅과 서버 실행을 담당합니다.

  • route.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const express = require('express');
    const router = express.Router();
     
    const mysql = require('mysql');
    const connection = mysql.createConnection(require('../config/dbconfig.js'));
     
    connection.connect((err) => {
    if(err) {
    console.log(err);
    return;
    }
    console.log( 'mysql connect completed' );
    });
    router.get('/', (req, res) => {
    const sql = 'SELECT * FROM Projects';
    connection.query(sql, (err, results, field) => {
    console.log(results); // 배열 형태로 결과가 떨어짐
    res.render('index', {
    layout: false, // express-ejs-layouts는 기본으로 layout.ejs가 설정되어야 하는데 이를 사용하지 않을 경우
    projects: results
    });
    });
    });
     
    module.exports = router;

route.js에서 mysql 모듈을 불러옵니다.
mysql 모듈의 mysql.createConnection을 사용하면 db에 접근할 수 있는 객체(?)를 생성할 수 있습니다. 이 메서드는 인자로 dbconfig.js에서 설정한 정보를 가져옵니다. 이제 우리는 connection 객체로 db에 접근할 수 있게 되었습니다. 다음으로 connection.connect()는 db 접속시 발생하는 이벤트입니다. 에러를 인자로 받아 db접속에 실패할 경우 어떠한 에러인지 판별할 수 있고 성공 로그를 남길수도 있습니다. 마지막으로 connection.query()입니다. 이 메서드는 db에 쿼리문을 전달하여 결과 값을 받아오는 기능을 합니다. 우리는 첫번째 인자로 string 형태의 쿼리문을 전달하였고 그에대한 callback의 2번째 인자로 결과를 가져왔습니다. 콘솔에 results의 로그를 남겨보면 맨 처음 추가했던 2개의 데이터가 배열 형태로 넘어오는 것을 볼 수 있습니다.

  • index.ejs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    </head>
    <body>
    <h1>프로젝트 리스트</h1>
    <table>
    <thead>
    <tr>
    <th>id</th>
    <th>description</th>
    <th>author</th>
    </tr>
    </thead>
    <tbody>
    <% projects.forEach((item) => { %>
    <tr>
    <td><%= item.id %></td>
    <td><%= item.description %></td>
    <td><%= item.author %></td>
    </tr>
    <% }); %>
    </tbody>
    </table>
    </body>
    </html>

route.js에서 res.render부분을 보면 쿼리문에대한 결과를 넘겨주는 것을 볼 수 있습니다. 그로인해 우리는 projects라는 이름의 변수를 사용할 수 있고, 그 변수는 하나의 배열입니다. index.ejs에서는 그 배열을 forEach 돌면서 뿌려주는 역할을 합니다.

이제 app.js를 실행하여 접속하면 2줄의 데이터를 볼 수 있습니다. db에 접속하여 데이터를 추가하거나 삭제 후 새로고침하면 이전과는 다른 화면을 볼 수 있게 되었지요.

참고

mysql

Node.js(express) - express ejs 템플릿

Render ejs

Directory

  • project
    • node_modules
    • views
      • index.ejs
      • layout.ejs
    • app.js
    • package.json
1
2
3
4
5
npm init
.
.
.
npm i --save express ejs express-ejs-layouts

프로젝트 폴더에 접속하여 npm init으로 package.json 파일을 생성 후 express와 ejs, express-ejs-layouts를 설치합니다. express-ejs-layouts는 express에서 ejs의 다양한 layout 기능을 추가적으로 제공해줍니다.

  • layout.ejs
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <!DOCTYPE html>
    <html lang="ko">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    *{margin: 0; padding: 0;}
    html, body {
    height: 100%;
    }
    </style>
    </head>
    <body>
    <header>
    헤더영역
    </header>
    <div id="main" style="border: 1px solid #dedede;">
    <%- body %>
    </div>
    <footer>
    푸터영역
    </footer>
    <%- script %>
    </body>
    </html>

view 페이지들의 layout 페이지입니다. <%- body %>부분에 컨텐츠가 들어가고, <%- script %>부분에 추출된 script 태그들이 들어갑니다. 설정 방법은 app.js에서 살펴보겠습니다.
code를 html형태로 했더니 하이라이팅이 조금 이상합니다 ㅠㅠ

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const express = require('express');
    const app = express();
    const expressLayouts = require('express-ejs-layouts');
     
    app.set('views',__dirname + '/views');
    app.set('view engine', 'ejs');
     
    // ejs-layouts setting
    app.set('layout', 'layout');
    app.set("layout extractScripts", true);
    app.use(expressLayouts);
     
    app.get('/', (req, res) => {
    res.render('index', {
    title: '타이틀 입니다.',
    description: '타이틀에 대한 설명이 들어오게 되겠지요?'
    })
    });
    app.listen(3000);

처음 등장하는 코드 위주로 알아보겠습니다.
app.set('views',__dirname + '/views'); 이 코드는 view 파일들이 있는 경로를 설정하는 영역입니다. __dirname은 현재 app.js가 위치한 경로를 알려주는 Node.js의 전역변수인데, 우리는 이 코드를 통해서 app.js와 같은레벨에 있는 views 라는 폴더에 템플릿 파일들이 있다고 app에게 알려준 것 입니다.

app.set('view engine', 'ejs'); 템플릿 엔진 ejs 사용하겠다고 선언한 것입니다. 이 선언을 함으로써 우리는 res.render 메소드에서 .ejs를 생략할 수 있게되었습니다.

app.set('layout', 'layout'); views/layout.ejs를 기본 레이아웃으로 설정합니다. layout.ejs의 <%- body %> 부분에 랜더링된 html 문자열이 들어갑니다.

app.set("layout extractScripts", true); 랜더링된 html에서 모든 script태그를 추출합니다. 이 script태그들은 layout.ejs에서 <%- script %> 부분에 들어가게 됩니다.

res.render('index', [,locals], [,callback]) index.ejs 파일을 랜더링 합니다. locals는 객체형태이며 이 객체의 프로퍼티들은 랜더링 되는 view 페이지에서 변수로 사용이 가능합니다.

  • index.ejs
    1
    2
    3
    <h1><%= title %></h1>
    <script>console.log('script tag between h1 and p but..')</script>
    <p><%= description %></p>

app.js 실행 후 접속하면 완성된 결과를 볼 수 있습니다. express-ejs-layouts 모듈은 이 외에도 꽤 다양한 기능을 제공하고 있으니 링크에 접속하여 한번 읽어보시기 바랍니다.

Node.js(express) - Router와 middleware

Router

express에서 router는 기본적으로 아래와 같은 형태를 가집니다.

1
app.METHOD(PATH, HANDLER)

  • METHOD: http 요청 메서드
  • PATH: 경로(엔드포인트)
  • HANDLER: 실행될 함수

몇가지 예를 들어보면

1
2
3
4
5
6
7
8
9
10
11
12
app.get('/home', function(req, res) {
res.send('home 접속');
});
app.post('/user', function(req, res) {
res.send('user에 대한 post 요청 처리');
});
app.put('/user', function(req, res) {
res.send('user에 대한 put 요청 처리');
});
app.delete('/user', function(req, res) {
res.send('user에 대한 delete 요청 처리');
});

가장 많이 사용되는 4가지 http 메서드에 대한 예제입니다.
보시면 /user 경로가 중복되는 경우가 있는데 이럴 경우 app.route(path)를 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
app.route('/user')
.post(function(req, res) {
res.send('user에 대한 post 요청 처리');
})
.put(function(req, res) {
res.send('user에 대한 put 요청 처리');
})
.delete(function(req, res) {
res.send('user에 대한 delete 요청 처리');
});

express.Router

라우터의 기본 사용방법을 보았으니 이를 모듈화 시키는 방법을 보겠습니다.

  • app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const express = require('express');
    const router = require('./route'); // 모듈화된 router를 불러옵니다.
    const app = express();
     
    app.use(router);
     
    // 1번쨰 매개변수로 받은 /hi에 대한 라우팅이 가능하도록 해줍니다. ex) /hi, /hi/about, /hi/user
    // app.use('/hi', router);
     
    app.listen(4000);
  • route.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const express = require('express');
    const router = express.Router();
     
    router.get('/', function(req, res) {
    res.send('Home 접속');
    });
    router.get('/about', function(req, res) {
    res.send('About 접속');
    });
    router.post('/user', function(req, res) {
    res.send('user에 대한 post 요청');
    });
     
    module.exports = router;

route.js 파일을 만들어서 router 모듈을 만들었습니다. 우리는 app.js에 router를 불러와 app.use()를 통하여 지정된 경로에 미들웨어로 마운트한 것 입니다. (경로가 없다면 모든 요청시마다 실행합니다)

Middleware(미들웨어)

express는 일련의 미들웨어(함수)들로 이루어져있습니다. 이 미들웨어는 요청, 응답 사이에서 부가적인 처리를 할 수 있고 다음 미들웨어의 실행권한을 가지게 됩니다. 이해가 부족한지 텍스트로 내용을 풀어쓰기가 쉽지가 않아 예제를 보는게 편할듯 합니다. express.Router() 예제에서 사용한 app.js를 이어서 사용하겠습니다.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const express = require('express');
    const router = require('./route.js');
    const app = express();
     
    app.use(function(req, res, next) {
    req.testValue = '안녕하세요.';
    console.log('1번');
    next(); // 다음 middleware 실행
    }, function(req, res, next) {
    console.log('2번');
    next(); // 다음 middleware 실행
    });
     
    app.get('/', function(req, res) {
    console.log('home', req.testValue);
    res.send('Home');
    });
    app.use('/path', router); // '/path'로 시작하는 경로의 경우에만 실행됩니다.
     
    app.listen(3000);

서버를 실행 시킨 뒤 localhost:3000에 접속하면 터미널에 3개의 콘솔이 찍힌 것을 볼 수 있습니다.

  • terminal
    1
    2
    3
    1번
    2번
    home 안녕하세요.

작성한 미들웨어가 위에서부터 순차적으로 실행된다것과(중요!) req.testValue를 보아 미들웨어 스택내에서 같은 request, response를 공유하고 있다는 것을 알 수 있습니다.

여기서 사용한 app.use()가 미들웨어를 설정하는 부분인데, (app.METHOD도 미들웨어 설정이라고 볼 수 있음)
이 메서드는 app.use([path,] callback [, callback...])이러한 형태를 가지게 됩니다.
path가 생략되어있다면 모든 요청에서 callback이 실행되고, 그렇지 않다면 해당 path로 시작하는 요청에서 callback이 실행됩니다. 또한 각 미들웨어는 다음에 실행되는 미들웨어의 실행 권한을 가지고 있습니다. 일반적으로는 예제에서 사용한 next라는 이름의 변수를 사용합니다. next()를 호출하지 않는다면 다음 미들웨어를 실행하지 않게 되겠지요. 만약 응답을 보내주지도 않았다면 요청이 온 상태에서 멈춰버리게 됩니다. ( 파비콘자리에 빙글빙글 도는 로딩바를 보실 수 있습니다 ㅠㅠ )

지금까지 직접 미들웨어를 만드는 방법을 알아보았습니다. 이제는 express에서 기본으로 제공하는 미들웨어와 써드파티 미들웨어를 사용하는 법을 알아보려 합니다. 마찬가지로 app.js를 그대로 사용하겠습니다.

우선 npm을 통하여 모듈을 설치합니다.

  • terminal

    1
    npm i --save body-parser
  • app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    const express = require('express');
    const router = require('./route.js');
    const app = express();
    const bodyParser = require('body-parser'); // 설치한 모듈을 불러옵니다.
     
    // set static file directory
    app.use(express.static('public'));
    // parse application/x-www-form-urlencoded
    app.use(bodyParser.urlencoded({ extended: false }));
    // parse application/json
    app.use(bodyParser.json());
     
    app.use(function(req, res, next) {
    req.testValue = '안녕하세요.';
    console.log('1번');
    next(); // 다음 middleware 실행
    }, function(req, res, next) {
    console.log('2번');
    next(); // 다음 middleware 실행
    });
     
    app.get('/', function(req, res) {
    console.log('home', req.testValue);
    res.send('Home');
    });
    app.use('/path', router); // '/path'로 시작하는 경로의 경우에만 실행됩니다.
     
    app.listen(3000);

express가 4.X 버전에 올라오면서 express.static을 제외한 기본 미들웨어들은 모두 별도의 모듈로 분리 되었습니다. 7번줄에서 사용한 이 메서드는 정적파일들을 제공해주는 역할을 합니다. 지난번 예제에서 staticMap을 만들어서 확장자에 따른 각 헤더값을 정해주는 작업을 이제는 하지 않아도 되는 것이지요.

다음으로는 써드파트 미들웨어입니다. npm을 통하여 사용할 모듈을 설치하고 app.use() 를 통해 미들웨어로써 마운트한 것입니다. 예제에서는 body-parser라는 모듈을 설치하였는데 이 모듈은 요청 바디를 파싱하여서 req.body 객체로 접근할 수 있도록 도와줍니다. bodyParser.urlencoded()는 헤더의 Content-Type이 application/x-www-form-urlencoded일 경우(form태그의 기본 인코딩 타입이다)에 대한 파싱을 해주고, bodyParser.json()은 마찬가지로 Content-Type이 application/json일 경우에 대한 파싱을 해줍니다. 추가로 bodyParser.urlencoded의 옵션으로 들어가는 extended는 값에 따라서 데이터 파싱을 querystring 라이브러리를 사용하는지 qs 라이브러리로 하는지의 차이를 가지고 있다고 합니다. 자세한 내용은 하단 참고링크를 걸어두겠습니다.

참고

Node.js(express) - express 프레임워크

express

express.js는 Node.js에서 사용하는 웹 앱 프레임워크입니다.
처음 express를 접해보았을 때는 버전이 3.X 가 나온지 얼마 안되었을 때 같은데 어느새 4.X로 올라왔고 Connect 모듈에 대한 종속성이 사라졌다고 합니다. ( 3.X -> 4.X로 버전 업을 할 경우 Connect에 종속된 미들웨어는 따로 처리를 해주셔야 합니다. )

설치

1
2
mkdir express-exam01
cd express-exam01

express 설치할 폴더를 생성 후 해당 폴더로 이동합니다.

1
npm init

npm init을 통해 해당 폴더를 npm project로 만들고 package.json을 생성합니다.

1
npm i express --save

npm을 이용하여 express를 설치 save 옵션을 통해 package.json의 dependencies에 express를 추가해줍니다. ( 임시로 사용할 것 이라면 save 옵션을 주지 않아도 괜찮습니다 )

실행

express-exam01폴더에 app.js를 생성합니다.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    const express = require('express');
    const app = express();
     
    app.get('/', function(req,res) {
    res.send('생성 완료');
    });
    app.listen(4000);

터미널을 통해 app.js를 실행 후 localhost:4000을 접속하면 웹 서버가 정상적으로 동작하는 것을 확인할 수 있습니다.

express()를 통하여 express 어플리케이션을 객체를 반환 합니다. 우리는 반환된 객체를 통해서 어플리케이션을 작성합니다.
4번 라인의 app.get 메서드를 사용하여 http get요청에 대한 route를 정의하였고, 5번라인의 res.send는 http 응답을 보내는 역할을 해줍니다. 특징이라면 헤더를 자동으로 지정해줍니다. 7번 라인의 app.listen 메서드를 사용하여 서버를 실행 시켰습니다. http 모듈의 listen과 동일한 기능을 수행합니다.

Node.js - Router 및 static file serve

라우팅

라우팅은 Http 요청 메소드(GET, POST 등)에 대한 요청을 어플리케이션이 응답하는 방법을 결정하는 것을 말합니다. 웹 페이지에 접속하는 것 또한 어플리케이션에 GET요청을 보내는 것 이지요.

static 파일

static 파일(정적 파일)은 html, css, image, js 와 같이 내용이 변함없이 고정 되어서 별도의 작업을 하지 않고 그대로 보내줄 수 있는 파일을 의미합니다.

Routing & Serve static file with node js

Directory

  • project
    • public
      • css
        • styles.css
      • images
        • image.jpg
    • views
      • 404.html
      • about.html
      • index.html
    • app.js
    • requestHandlers.js
    • router.js
    • server.js

예제

  • app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const server = require('./server');
    const router = require('./router');
    const requestHandlers = require('./requestHandlers');
     
    var handle = {}
    handle['/'] = requestHandlers.home;
    handle['/about'] = requestHandlers.about;
     
    server.start(router.route, handle);

    필요로하는 모든 기능을 app.js에서 수행하기에는 너무 넓은 영역입니다. 각 기능들을 common.js 표준인 exports, module.exports를 통해 모듈화하여서 require을 통해 불러옵니다. 그리고 handle 객체를 만들어서 server를 실행 시키는 함수에 route함수와 함께 넘겨줍니다. 이 handle객체의 key값은 요청 메서드의 경로입니다.

  • server.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const http = require('http');
    const url = require('url');
    const fs = require('fs');
     
    const start = (route, handle) => {
    http.createServer((req, res) => {
    const pathname = url.parse(req.url).pathname;
    route(handle, pathname, res, req);
    }).listen(4000);
    }
     
    exports.start = start;

    여태까지는 createServer메서드의 매개변수로 주고있는 request 이벤트 핸들러에서 response(응답)을 처리하였지만, 이제는 route 함수로 response 객체와 handler 들을 넘겨줍니다.

  • router.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    const fs = require('fs');
    const path = require('path'); // file 경로를 다루기 위한 모듈 (기본)
     
    const route = (handle, pathname, res, req) => {
    const extension = path.extname(pathname); // 확장자를 구하는 메서드
    const staticMap = {
    '.ico': 'image/x-icon',
    '.html': 'text/html',
    '.js': 'text/javascript',
    '.json': 'application/json',
    '.css': 'text/css',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.mp3': 'audio/mpeg',
    }
    const staticPath = __dirname + '/public';
     
    if (typeof handle[pathname] === 'function') {
    handle[pathname](res, req);
    } else {
    if( staticMap[extension] ) {
    //static file
    fs.readFile( staticPath + pathname, (err, data) => {
    res.writeHead(200, {'Content-Type': staticMap[extension]});
    res.end(data);
    });
    } else {
    fs.readFile('./views/404.html', (err, data) => {
    res.writeHead(404, {'Content-Type': 'text/html'});
    res.end(data);
    });
    }
    }
    }
     
    exports.route = route;

    route는 pathname과 일치하는 handler를 가지고 있다면 handle 함수로 다시 response(응답) 객체를 넘겨주고 그렇지 않다면 여기서 처리해줍니다. handler가 가지고 있지 않는 경우라하면 요청이 static 파일이거나 혹은 유효하지 않은 요청을 하였을 경우입니다.
    staticMap을 만들어서 static 파일들일 경우와 아닐 경우를 한번 더 분기 해줍니다.

  • requestHandlers.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const fs = require('fs');
     
    module.exports = {
    home: (res) => {
    fs.readFile('./views/index.html', (err, data) => {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(data)
    });
    },
    about: (res) => {
    fs.readFile('./views/about.html', (err, data) => {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(data);
    });
    }
    }

    라우터에서 호출된 각 handler들은 html파일을 읽어서 넘겨받은 response객체를 통해 브라우저에 보내주는 역할을 하게 됩니다.

  • index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="/css/styles.css">
    </head>
    <body>
    <h1>Home</h1>
    <a href="/about">About으로 이동</a>
    <br/>
    <img src="/images/image.jpg" alt="">
    </body>
    </html>

    localhost:4000을 접속하면 pathname은 /로 정의됩니다, 우리가 라우터로 넘겨준 handle객체에서 / 라는 값은 requestHandlers.js의 home이라는 function을 호출하기 때문에 index.html을 응답해줄 수 있습니다. 반대로 이 index.html에서 요청하는 styles.css와 image.jpg는 handle객체가 가지고 있지 않은 값 이지만 staticMap을 통해서 static한 파일임을 알 수 있으므로 정상적인 헤더값을 정의해줄 수 있게 됩니다.

about.html과 404.html또한 views 폴더에 생성한 뒤 app.js를 실행합니다.
localhost:4000을 접속하면 index.html
localhost:4000/about을 접속하면 about.html
localhost:4000/abc123을 접속하면 404.html 을 보여주는 것을 확인할 수 있습니다.


맺음

Node.js의 기본 모듈만을 사용하여 Router와 static 파일들을 제공하는 방법을 알아보았습니다. 외부모듈이나 express와 같은 framework를 사용하면 더 쉽게 사용할 수 있었겠지만 이 부분은 차차 공부해볼려 합니다.

참고

Simple node server

Node.js - File System 모듈(readFile)

fs 모듈

Node.js 는 fs(file system) 모듈을 이용하여 파일과 디렉토리에 관련된 작업을 할 수 있습니다. 이 fs 모듈의 모든 메서드들은 동기/비동기 양식을 지원하고 있습니다. 동기 방식은 값을 함수에서 바로 return 해주지만 single thread를 사용하는 Node.js는 이 작업이 실행되는 동안은 다른 작업이 실행되는것을 막습니다. 비동기 방식은 다른 작업이 실행되는 것을 막지는 않지만 보장된 순서가 없어 오류가 발생하기 쉽습니다. 어떠한 방식을 사용할지는 경우에따라 본인 판단 후 사용하도록 합시다.

html 읽기

Directory

  • project
    • app.js
    • index.html

예제

  • index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    </head>
    <body>
    <p>안녕하세요.</p>
    </body>
    </html>
  • app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const http = require('http');
    const fs = require('fs');
     
    const server = http.createServer((req, res) => {
    fs.readFile('index.html', (err, data) => {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(data);
    });
    });
     
    server.listen(4000, () => {
    console.log('server running');
    });

app.js 를 실행 후 localhost:4000 을 접속하면 index.html 페이지를 볼 수 있을 것 입니다.


설명

  1. readFile
    비동기 방식으로 파일을 읽는 메서드 입니다. file, option, callback 3개의 매개변수를 가지며 callback 을 통하여 error 또는 data를 반환합니다.
    data는 기본적으로 Buffer 객체를 반환 하지만 option을 통하여 encoding type을 지정할 수 있습니다.
    fs.readFile('index.html', 'utf-8', (err, data) => {}); 이와 같이 encoding type을 utf-8로 변환 후 console.log 를 통하여 data를 찍어보면 html 파일을 string 형태로 보여줄 것 입니다.

  2. writeHead
    시작하기에서 언급했듯이 응답값의 헤더를 작성합니다. 이전 예제에서와는 다르게 html 문서를 응답해주므로 Content-Type 을 text/html로 작성하였습니다.
    이는 MIME 형식에서 정의한 문자열 입니다. 자세한 내용은 링크를 참조

  3. end
    end 메서드가 data를 가지고 있는경우 response.write(data); 를 실행 후 end를 호출 한 것 과 같은 효과를 줍니다. write 메서드는 응답 body에 chunk를 보내주는 역할을 합니다.

가장 자주 쓰이는 파일을 읽는 방법만 알아보았지만 이 외에도 생성, 삭제, 이름변경 등 많은 기능들을 제공하니 api 문서를 참조해보시기를 바랍니다.
docs

Node.js 시작하기

설치

Node js 공식 홈페이지 에서 설치,
이 외 여러 package manager를 사용하여 설치가 가능합니다. 참조


Hello World

Node.js 어플리케이션은 javascript로 구현되며, .js 확장자를 사용합니다.
http 통신을 하는 hello world 어플리케이션을 만들기 위해 app.js 파일을 생성합니다.

  • app.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const http = require('http'); // http 모듈을 불러온다.
    const port = 4000;
     
    http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
    }).listen(port, () => {
    // callback
    console.log('server running');
    });

Node.js 6.x 는 es6의 지원이 원활 하므로 var 이 아닌 const 를 사용 하였습니다. (자세한 사항은 http://node.green/ 를 참조)

  • terminal
    1
    node app

app.js 를 생성 후 터미널에 접속하여 node 실행 명령어를 입력합니다.
server running 이라는 표시가 뜰 것 이고 localhost:4000 에 접속하면 hello world 어플리케이션이 완성 되어있는 것을 볼 수 있습니다.


설명

  1. http 모듈
    app.js 의 1번 line을 보면 require('http'); 를 통하여 http라는 변수(상수)에 선언합니다. http 모듈은 HTTP 웹 서버와 관련된 기능들을 담은 모듈로써 동작됩니다.

  2. http.createServer()
    http 객체의 createServer 메서드는 server 객체를 생성하도록 해줍니다.
    반환된 server 객체는 listen, close 메서드를 사용하여 서버를 실행, 종료 할 수 있고, 그 외 request, connection, close 등의 이벤트를 연결할 수 있습니다.

  3. requestListener
    위에서 언급한 server 객체의 request(요청) 이벤트 핸들러는 따로 지정할 필요 없이 app.js 의 예제와 같이, createServer 메서드의 매개변수로 넘겨 줄 수 있습니다. 이 이벤트는 request와 response 라는 객체를 매개변수로 전달 받습니다.
    그 중 response 객체의 writeHead는 요청에 대한 응답 헤더를 작성하고, end는 응답헤더와 본문이 전달 되었음을 server에 알려줍니다(end 메서드는 각 응답마다 반드시 호출 되어야 합니다).