디렉토리 구조

gulp-es6-webpack/ 
├── .babelrc 
├── dist 
├── node_components 
├── server 
│       └── main.js 
└── src 
    ├── css 
    │    └── style.css 
    ├── images 
    │      └── image.png 
    ├── index.html 
    └── js 
        └── main.js 
├── gulpfile.babel.js 
├── index.js 
├── package.json

src 폴더 에는 Front-end 사이드에서 사용할 파일들이 있으며 gulp에서 minify 하여 dist 폴더에 변환된 파일들을 저장 하게 된다.gulpfile 을 추가적으로 작성하기 전에 저희 예제 프로젝트의 디렉토리 구조를 알아보자.

server 폴더의 경우 server 사이드에서 사용 할 파일들이 있습니다. 이 프로젝트에선 server 부분에서도 ES6 를 쓸 것인데, 이에 관해서는 나중에 설명하기로 하자.

js 파일 및 css 파일들은 마음대로 작성하고.

image 또한 원하는 이미지를 넣자.

 

Gulp API

gulp 에는 4가지의 주요 API가 있다.

  1. gulp.task
  2. gulp.src
  3. gulp.dest
  4. gulp.watch

gulp.task(name [, deps, fn]) 는 gulp가 처리할 작업‘ 을 정의한다.

인수 name 은 string 형태로서 task의 이름을 지정하며, deps fn 은 optional 인수로서, 생략되어도 되는 인수다.
deps 는 task name 의 배열 형태이며 이 인수가 전달 될 시, 이 배열 안에 있는 task들을 먼저 실행 한다음에,
함수형태로 전달되는 fn 을 실행한다.

gulp.task('hello', () => { 
    console.log('hello');
});

gulp.task('world', ['hello'], () => {
    console.log('world');
});

이렇게 만든 task 는, 명령어 gulp name 을 통해 커맨드라인에서 특정 task를 실행 할 수 있다.

$ gulp world 
[15:20:52] Requiring external module babel-register 
[15:20:54] Using gulpfile ~/node_tutorial/gulp-es6-webpack/gulpfile.babel.js 
[15:20:54] Starting 'hello'... 
hello 
[15:20:54] Finished 'hello' after 268 μs 
[15:20:54] Starting 'world'... 
world 
[15:20:54] Finished 'world' after 120 μs

gulp.src(globs[, options]) 는 어떤 파일을 읽을지 정한다.gulp 명령어를 실행 할 때, name 을 명시하지 않으면 default task 가 실행된다.

인수 glob 은 string 형태나 array 형태입니다. node-glob syntax 를 사용하여 “**/*.js” 이런식으로 여러 파일을 한꺼번에 지정 할 수 있다.
options는 Object 형태이며 node-glob에 전달 할 옵션이다. 자세한 내용은 GULP API를 확인해주자.

이 함수가 리턴한 객체에서는 .pipe 를 통하여 다른 플러그인을 사용해 변환 할 수 있다.

이에 대한 예제는 잠시 후 플러그인을 설치하고 gulpfile 을 작성 할 때 알아보자.

gulp.dest(path[, options]) 는 어디에 저장할지 정한다.

path 는 디렉토리를 입력하며,

options는 객체로서 { cwd: ____, mode: ____ } 형태다.
cwd 는 현재 디렉토리 위치로서 .path가 /build/ 이런식으로 상대적일때 현재 디렉토리를 따로 설정하고 싶을 때 사용하며, mode 는 파일권한 (기본 : “0777”) 이다.

이에 대한 예제 또한 gulpfile을 작성 할 때 알아보겠다.

gulp.watch(glob[, opts], tasks/cb) 는 전달된 glob에 해당하는 파일들을 주시하고있다가, 변동이 있을 시 tasks를 실행한다.

인수 tasks  는 task name의 배열형태다. 배열 형태가 아닐 땐 event를 파라미터로 가지고있는 콜백함수 cb 를 작성한다.
opts는 gulp에서 사용하는 라이브러리인 gaze 에 전달 할 옵션이다.

매뉴얼에 적혀있는 예제를 한번 훑어보자:

var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

// OR

gulp.watch('js/**/*.js', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

 

gulpfile작성

gulp에 대한 간단한 설명을 봤으니, 이제 gulpfile을 작성해보자

  • minify javascript
  • minify css
  • minify html
  • compress image

gulp 자체에서는 위 기능들을 지원하지 않는다. 대신 gulp 플러그인들이 위 역할들을 대신해준다.

 

플러그인 설치

플러그인 검색은 Gulpjs 홈페이지 에서 할 수 있다.

사용 할 플러그인은 다음과 같다

 

del 모듈은 gulp 플러그인은 아니다. gulp 플러그인으로 제작된 모듈이 아니더라도 gulpfile 내에서 사용 할 수는 있다.
이 모듈은 특정 디렉토리를 삭제해주는 플러그인이다. 동기식으로 삭제 할 수 있는 기능을 가지고 있고.

gulp 작업이 실행 될 때 마다 기존 dist 디렉토리에 있는 파일들을 삭제해줘야 하기 때문에 이 플러그인을 사용한다.

npm 을 통하여 설치해보자.

npm install --save-dev gulp-uglify gulp-clean-css gulp-htmlmin gulp-imagemin del

 

설치가 끝났다면 gulpfile 상단에 위 플러그인들을 import 해주자.

import uglify from 'gulp-uglify';
import cleanCSS from 'gulp-clean-css';
import htmlmin from 'gulp-htmlmin';
import imagemin from 'gulp-imagemin';
import del from 'del';

 

디렉토리 정의

const DIR = {
    SRC: 'src',
    DEST: 'dist'
};

const SRC = {
    JS: DIR.SRC + '/js/*.js',
    CSS: DIR.SRC + '/css/*.css',
    HTML: DIR.SRC + '/*.html',
    IMAGES: DIR.SRC + '/images/*'
};

const DEST = {
    JS: DIR.DEST + '/js',
    CSS: DIR.DEST + '/css',
    HTML: DIR.DEST + '/',
    IMAGES: DIR.DEST + '/images'
};

 

Task 작성

minify javascript

gulp.task('js', () => {
    return gulp.src(SRC.JS)
           .pipe(uglify())
           .pipe(gulp.dest(DEST.JS));
});

코드를 저장하고 실행해보자.

$ gulp js 
[16:48:43] Requiring external module babel-register 
[16:48:43] Working directory changed to ~/node_tutorial/gulp-es6-webpack 
[16:48:43] Using gulpfile ~/node_tutorial/gulp-es6-webpack/gulpfile.babel.js 
[16:48:43] Starting 'js'... 
[16:48:43] Finished 'js' after 74 ms


minify css
dist/js 폴더에 파일이 저장되었는지도 확인해보자. 그렇다면 다음 단계로 넘어가자

gulp.task('css', () => {
    return gulp.src(SRC.CSS)
           .pipe(cleanCSS({compatibility: 'ie8'}))
           .pipe(gulp.dest(DEST.CSS));
});

 

minify html

gulp.task('html', () => {
    return gulp.src(SRC.HTML)
          .pipe(htmlmin({collapseWhitespace: true}))
          .pipe(gulp.dest(DEST.HTML))
});

 

compress images

gulp.task('images', () => {
    return gulp.src(SRC.IMAGES)
           .pipe(imagemin())
           .pipe(gulp.dest(DEST.IMAGES));
});

 

clean

gulp.task('clean', () => {
    return del.sync([DIR.DEST]);
});

 

default

기본 gulp task 를 정의 할 차례다. 기본 task 에서는 위에 만든 여러 task 들을 실행하도록 설정한다.

gulp.task('default', ['clean', 'js', 'css', 'html', 'images'], () => {
    gutil.log('Gulp is running');
});

gulp 명령어를 입력해서 테스트 해보자. 오류가 없다면 Watch Watch를 작성해보자

 

WATCH 작성하기

watch는 특정 디렉토리 및 파일들을 감시하고 있다가 변동이 감지 될 시, 지정한 task 를 실행시키는 기능이다.

gulp.task('watch', () => {
    gulp.watch(SRC.JS, ['js']);
    gulp.watch(SRC.CSS, ['css']);
    gulp.watch(SRC.HTML, ['html']);
    gulp.watch(SRC.IMAGES, ['images']);
});

 

watch 를 작성하는건 위와 같이 간단하다. 첫번째 인수로 전달된 값에 해당하는 파일들을 감시하고 있다가, 두번째 인수로 전달된 task 를 실행한다. 어떤 파일이 변경되었는지 기록하고싶다면, 코드를 다음과 같이 수정하자.

gulp.task('watch', () => {
    let watcher = {
        js: gulp.watch(SRC.JS, ['js']),
        css: gulp.watch(SRC.CSS, ['css']),
        html: gulp.watch(SRC.HTML, ['html']),
        images: gulp.watch(SRC.IMAGES, ['images'])
    };

    let notify = (event) => {
        gutil.log('File', gutil.colors.yellow(event.path), 'was', gutil.colors.magenta(event.type));
    };

    for(let key in watcher) {
        watcher[key].on('change', notify);
    }
});

 

그리고, ‘watch’ 를 default task 부분의 deps 배열에 넣어준다.

gulp.task('default', ['clean', 'js', 'css', 'html', 'images', 'watch'], () => {
    gutil.log('Gulp is running');
});

 

이제 테스트를 해보자 새로운 screen 을 열어서 gulp 를 실행 시킨 다음에 파일을 수정해보자.

$ screen -S gulp 
##################################################################### 
$ gulp 
[15:25:49] Requiring external module babel-register 
[15:25:50] Using gulpfile ~/node_tutorial/gulp-es6-webpack/gulpfile.babel.js 
[15:25:50] Starting 'clean'... 
[15:25:50] Finished 'clean' after 11 ms 
[15:25:50] Starting 'js'... 
[15:25:50] Starting 'css'... 
[15:25:50] Starting 'html'... 
[15:25:50] Starting 'images'... 
[15:25:50] Starting 'watch'... 
[15:25:50] Finished 'watch' after 20 ms 
[15:25:50] Finished 'html' after 154 ms 
[15:25:50] Finished 'js' after 178 ms 
[15:25:50] Finished 'css' after 165 ms 
[15:25:52] gulp-imagemin: Minified 1 image (saved 17.06 kB - 13.2%) 
[15:25:52] Finished 'images' after 2 s 
[15:25:52] Starting 'default'... 
[15:25:52] Gulp is running 
[15:25:52] Finished 'default' after 308 μs 
##################################################################### 
# CTRL+A+D, edit files ... 
##################################################################### 
$ screen -r gulp 
##################################################################### 
[15:28:02] File /home/vlpt/node_tutorial/gulp-es6-webpack/src/index.html was changed 
[15:28:02] Starting 'html'... 
[15:28:02] Finished 'html' after 35 ms 
[15:28:09] File /home/vlpt/node_tutorial/gulp-es6-webpack/src/js/main.js was changed 
[15:28:09] Starting 'js'... 
[15:28:09] Finished 'js' after 11 ms 
[15:28:21] File /home/vlpt/node_tutorial/gulp-es6-webpack/src/css/style.css was changed 
[15:28:21] Starting 'css'... 
[15:28:21] Finished 'css' after 29 ms

성공이다.


WRITTEN BY
Clasha

,

소개

Node.js 환경에서 웹 어플리케이션을 만들다보면, 일일히 수작업으로 하기에 귀찮은 작업들이 존재한다.

예를들어서, ____.min.js, ____.min.css파일이 있다. whitespace, newline 과 같이 없어도 지장이 되지 않는 문자들을

제거함으로서 페이지 렌더링 성능도 (비록 큰 차이는 아니지만) 를 늘리고 트래픽도 많이 아낄 수 있다.

jQuery 2.1.3 버전의 경우 uncompressed 와 minified 의 파일 사이즈가 159KB 차이가 나고.

하루 방문자가 1000명이라면, 155MB 의 트래픽을 아낄 수 있다. 그만큼 중요한 file minification 작업을 js 와 css를 수정 할 때마다 수동으로 실행해야 한다면. 귀찮을 수 밖에 없다

다른 귀찮음의 예로는, Node.js 프로젝트를 작성하면서 .js 파일을 수정 할 때마다 서버를 재시작 해야했다.

개발자들의 이런 귀차니즘을 해결하기 위한 도구가 바로 gulp.js 다. 위에 설명 된 것 외에도 많은 작업들을 다 자동으로 해준다.

 

설치하기

1.Gulp전역(Global)설치

$ sudo npm install -g gulp

 

도중에  graceful-fs 와 lodash 에 관한 경고가 뜨면, 최신버전으로 설치해주자.

sudo npm install -g graceful-fs lodash 
/usr/local/lib 
├── graceful-fs@4.1.3 
└── lodash@4.11.2

 

2.프로젝트 폴더에서 npm init

$ npm init

 

3.gulp와 gulp-util 를 devDependencies 로 모듈 설치

(gulp-util 은 gulp에서 로그를 쉽게 기록 할 수 있게 해준다)

$ npm install -save-dev gulp gulp-util

 

4. babel-core 와 babel-preset-es2015 를  devDependencies 로 모듈 설치

$ npm install --save-dev babel-core babel-preset-es2015

위 모듈들은 gulp에서 ES6 를 사용 할 때 필요한 모듈들이다.

 

5. .babelrc 파일 생성

{
  "presets": ["es2015"]
}

스크립트를 변환해주는 모듈인 babel 의 설정이다.
ES6 문법을 사용하겠다는 의미다.

 

6. gulpfile.babel.js 작성

'use strict';

import gulp from 'gulp';
import gutil from 'gulp-util';

gulp.task('default', () => {
    return gutil.log('Gulp is running');
});

gulpfile 은 gulp 에서 어떤 작업들을 할 지 정의해준다.

 

'use strict';는 JavaScript 코드의 안정성을 위하여 문법검사를 더 확실하게 하겠다는 의미다. 자세한 내용은 “자바스크립트에서 strict mode를 사용해야 하는 이유”를 참고하자.

gulp 실행

기본 설정 완료

$ gulp 
[03:18:18] Requiring external module babel-register 
[03:18:19] Using gulpfile ~/node_tutorial/gulp-es6-webpack/gulpfile.babel.js 
[03:18:19] Starting 'default'... 
[03:18:19] Gulp is running 
[03:18:19] Finished 'default' after 7.57 ms

WRITTEN BY
Clasha

,

Schema & Model

 

Schema

schema는 document의 구조가 어떻게 생겼는지 알려주는 역할을 한다.

schema를 만드는 방법은 다음과 같다. 이 코드를 models/book.js 에 입력하자.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var bookSchema = new Schema({
    title: String,
    author: String,
    published_date: { type: Date, default: Date.now  }
});

module.exports = mongoose.model('book', bookSchema);

schema에서 사용되는 SchemaType은 총 8종류가 있습니다.

  1. String
  2. Number
  3. Date
  4. Buffer
  5. Boolean
  6. Mixed
  7. Objectid
  8. Array

 

Model

model은 데이터베이스에서 데이터를 읽고, 생성하고, 수정하는프로그래밍 인터페이스를 정의한다.

// DEFINE MODEL
var Book = mongoose.model('book', bookSchema);

첫번째 인자 'book’ 은 해당 document가 사용 할 collection의 단수적 표현이다. 즉, 이 모델에서는 ‘books’ collection 을 사용하게 되며. 이렇게 자동으로 단수적 표현을 복수적(plural) 형태로 변환하여 그걸 collection 이름으로 사용한다. collection 이름을 plural 형태로 사용하는건 mongodb의 네이밍컨벤션 중 하나다.

만약에, collection 이름을 임의로 정하고 싶다면, schema 를 만들 때 따로 설정해야 한다.

var dataSchema = new Schema({..}, { collection: 'COLLECTION_NAME' });

model을 만들고 나면, model 을 사용하여 다음과 같이 데이터를 데이터베이스에 저장하거나 조회 할 수 있습니다.

var book = new Book({
    name: "NodeJS Tutorial",
    author: "him"
});
book.save(function(err, book){
    if(err) return console.error(err);
    console.dir(book);
});

이와 같이 model을 사용하여 데이터베이스와 연동하는 자세한 방법에 대해서는 다음 섹션에서 다루도록 하자.

저희는 model을 models/bear.js 를 모듈화해서 만들게 할 것이므로, 다음과 같이 해당파일의 마지막줄에서 model을 모듈화한다..

// models/book.js

var Schema = mongoose.Schema;

var bookSchema = new Schema({
    title: String,
    author: String,
    published_date: { type: Date, default: Date.now  }
});

module.exports = mongoose.model('book', bookSchema);

app.js에서 이 모듈을 불러와보겠다.

// app.js
// ...
// CONNECT TO MONGODB SERVER
// ...
// DEFINE MODEL
var Book = require('./models/book');
// ...

 

CRUD(Create, Retrieve, Update, Delete)

Create (POST /api/books)

book 데이터를 데이터베이스에 저장하는 API다.

app.post('/api/books', function(req, res){
    var book = new Book();
    book.title = req.body.name;
    book.author = req.body.author;
    book.published_date = new Date(req.body.published_date);
    book.save(function(err){
        if(err){
            console.error(err);
            res.json({result: 0});
            return;
        }

        res.json({result: 1});
    });
});

.save 메소드는 데이터를 데이터베이스에 저장한다. err 을 통하여 오류처리가 가능하고.

이 API 에서는 데이터 저장에 성공하면 result: 1을, 실패하면 result: 0 을 반환한다.

(REST API 테스팅에 사용된 어플리케이션은 크롬 확장 프로그램 Insomnia 다)

 

Retrieve (GET /api/books)

모든 book 데이터를 조회하는 API

// GET ALL BOOKS
app.get('/api/books', function(req,res){
    Book.find(function(err, books){
        if(err) return res.status(500).send({error: 'database failure'});
        res.json(books);
    })
});

데이터를 조회 할 때는 find() 메소드가 사용된다.
query를 파라미터 값으로 전달 할 수 있으며, 파라미터가 없을 시, 모든 데이터를 조회한다.

데이터베이스에 오류가 발생하면 HTTP Status 500 과 함께 에러를 출력한다.

 

Retrieve (GET /api/books/:book_id)

데이터베이스에서 Id 값을 찾아서 book document를 출력한다.

// GET SINGLE BOOK
app.get('/api/books/:book_id', function(req, res){
    Book.findOne({_id: req.params.book_id}, function(err, book){
        if(err) return res.status(500).json({error: err});
        if(!book) return res.status(404).json({error: 'book not found'});
        res.json(book);
    })
});

하나의 데이터만 찾을 것이기 때문에, findOne 메소드가 사용되었다.

오류가 발생하면 500, 데이터가 없으면 404 HTTP Status 와 함께 오류를 출력한다.

 

Read (GET /api/books/author/:author)

author 값이 매칭되는 데이터를 찾아 출력

// GET BOOKS BY AUTHOR
app.get('/api/books/author/:author', function(req, res){
    Book.find({author: req.params.author}, {_id: 0, title: 1, published_date: 1},  function(err, books){
        if(err) return res.status(500).json({error: err});
        if(books.length === 0) return res.status(404).json({error: 'book not found'});
        res.json(books);
    })
});

find() 메소드에서 첫번째 인자에는 query 를, 두번째는 projection 을 전달해주었다.
이를 통하여 author 값으로 찾아서 title 과 published_date 만 출력한다.
(만약에 projection이 생략되었다면 모든 field 를 출력한다.)

 

Update (PUT /api/books/:book_id)

book_id 를 찾아서 document를 수정

// UPDATE THE BOOK
app.put('/api/books/:book_id', function(req, res){
    Book.findById(req.params.book_id, function(err, book){
        if(err) return res.status(500).json({ error: 'database failure' });
        if(!book) return res.status(404).json({ error: 'book not found' });

        if(req.body.title) book.title = req.body.title;
        if(req.body.author) book.author = req.body.author;
        if(req.body.published_date) book.published_date = req.body.published_date;
        book.save(function(err){
            if(err) res.status(500).json({error: 'failed to update'});
            res.json({message: 'book updated'});
        });
    });
});

데이터를 수정 할 땐, 데이터를 먼저 찾은 후, save() 메소드를 통하여 수정하면 된다다.
update하는 방법은 이 외에도 다른 방법이 있는데요, 만약 어플리케이션에서 기존 document를 굳이 조회 할 필요가없다면 update() 메소드를 통하여 바로 document를 업데이트 할 수 있다.
아래 코드는 코드와 같은 동작을 하지만 업데이트하는 과정에서 document를 조회 하지 않는다.

// UPDATE THE BOOK (ALTERNATIVE)
app.put('/api/books/:book_id', function(req, res){
    Book.update({ _id: req.params.book_id }, { $set: req.body }, function(err, output){
        if(err) res.status(500).json({ error: 'database failure' });
        console.log(output);
        if(!output.n) return res.status(404).json({ error: 'book not found' });
        res.json( { message: 'book updated' } );
    })
});

여기서 output 은 mongod 에서 출력하는 결과물이다.

{ 
    ok: 1, 
    nModified: 0,
    n: 1
}

여기서 nModified는 변경한 document 갯수, n은 select된 document 갯수이다.
update() 를 실행하였을 떄, 기존 내용이 업데이트 할 내용과 같으면 nModified 는 0 으로 되기 때문에,
n 값을 비교하여 성공여부를 판단한다.

 

Delete (PUT /api/books/:book_id)

book_id를 찾아서 document를 제거

// DELETE BOOK
app.delete('/api/books/:book_id', function(req, res){
    Book.remove({ _id: req.params.book_id }, function(err, output){
        if(err) return res.status(500).json({ error: "database failure" });

        /* ( SINCE DELETE OPERATION IS IDEMPOTENT, NO NEED TO SPECIFY )
        if(!output.result.n) return res.status(404).json({ error: "book not found" });
        res.json({ message: "book deleted" });
        */

        res.status(204).end();
    })
});

document를 제거 할 땐 remove() 메소드가 사용된다.

DELETE 는 idempotent(어떤 과정을 몇번이고 반복 수행 하여도 결과가 동일함; 즉 삭제한 데이터를 삭제하더라도, 존재하지 않는 다큐먼트를 제거 시도를하더라도 달라질게 없음) 하므로, 성공하였을떄나 실패하였을때나 결과값이 같다. 여기서 204 HTTP status 는 No Content 로서, 요청한 작업을 수행하였고 데이터를 반환 할 필요가 없다는것을 의미한다.6~9 번줄은 실제로 존재하는 데이터를 삭제하였는지 확인해주는 코드이나, 그럴 필요가 없으므로 주석처리되었다.


WRITTEN BY
Clasha

,

개요

Mongoose는 MongoDB 기반 ODM(Object Data Mapping) Node.JS 전용 라이브러리다. ODM은 데이터베이스와 객체지향 프로그래밍 언어 사이 호환되지 않는 데이터를 변환하는 프로그래밍 기법이고. 즉 MongoDB 에 있는 데이터를 여러분의 Application에서 JavaScript 객체로 사용 할 수 있도록 해준다.

 

프로젝트 생성 및 패키지 설치

프로젝트 생성

우선 npm init 을 통하여 package.json 을 생성한다. 엔터를 계속 눌러 설정값은 기본값으로 해주자.

$ npm init

 

패키지 설치

1.express: 웹 프레임워크

2.body-parser: 데이터 처리 미들웨어

3.mongoose: MongoDB 연동 라이브러리

$ npm install --save express mongoose body-parser

명령어를 입력하면 자동으로 패키치 설치하고, package.json파일에 패키지 리스트를 추가한다

 

서버 설정

디렉토리 구조

- models/
----- book.js
- node_modules/
- routes
----- index.js
app.js
package.json

 

웹 서버 생성

이 서버에 만들 API 목록은 다음과 같다.

ROUTE METHOD DESCRIPTION
/api/books GET 모든 book 데이터 조회
/api/books/book_id GET _id값으로 데이터 조회
/api/book/author/author GET author 값으로 데이터 조회
/api/books POST book 데이터 생성
/api/books/book_id PUT book 데이터 수정
/api/books/book_id DELETE book 데이터 제거

app.js

// app.js

// [LOAD PACKAGES]
var express     = require('express');
var app         = express();
var bodyParser  = require('body-parser');
var mongoose    = require('mongoose');

// [CONFIGURE APP TO USE bodyParser]
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// [CONFIGURE SERVER PORT]
var port = process.env.PORT || 8080;

// [CONFIGURE ROUTER]
var router = require('./routes')(app)

// [RUN SERVER]
var server = app.listen(port, function(){
 console.log("Express server has started on port " + port)

다음은 router를 만들어 보자.

router/index.js

// routes/index.js
module.exports = function(app)
{
    // GET ALL BOOKS
    app.get('/api/books', function(req,res){
        res.end();
    });
    // GET SINGLE BOOK
    app.get('/api/books/:book_id', function(req, res){
        res.end();
    });
    // GET BOOK BY AUTHOR
    app.get('/api/books/author/:author', function(req, res){
        res.end();
    });
    // CREATE BOOK
    app.post('/api/books', function(req, res){
        res.end();
    });
    // UPDATE THE BOOK
    app.put('/api/books/:book_id', function(req, res){
        res.end();
    });
    // DELETE BOOK
    app.delete('/api/books/:book_id', function(req, res){
        res.end();
    });
}

 

Mongo DB 연결

/ app.js
// ......
var mongoose    = require('mongoose');
// ......
// [ CONFIGURE mongoose ]
// CONNECT TO MONGODB SERVER
var db = mongoose.connection;
db.on('error', console.error);
db.once('open', function(){
    // CONNECTED TO MONGODB SERVER
    console.log("Connected to mongod server");
});
mongoose.connect('mongodb://localhost/mongodb_tutorial');

// ......

mongoose.connect() 메소드로 서버에 접속을 할 수 있으며, 따로 설정 할 파라미터가 있다면 다음과 같이 uri를 설정해주자.

mongoose.connect('mongodb://username:password@host:port/database?options...');

(사용DB mongodb_tutorial db)

(길어서 두번으로 나눠서 작성할 예정)


WRITTEN BY
Clasha

,

Express-session

Express-session은 Express 프레임워크에서 세션을 관리하기 위해 사용하는 미들웨어다.

Express-session을 통해 로그인과 로그아웃을 구현해 보자.

 

Express에 적용

var session = require('express-session');

app.use(session({
 secret: '@#@$MYSIGN#@$#$',
 resave: false,
 saveUninitialized: true
}));

secret – 쿠키를 임의로 변조하는것을 방지하기 위한 값. 이 값을 통하여 세션을 암호화 하여 저장한다.

resave – 세션을 언제나 저장할 지 (변경되지 않아도) 정하는 값이다. express-session documentation에서는 이 값을 false 로 하는것을 권장하고 필요에 따라 true로 설정한다..

saveUninitialized – 세션이 저장되기 전에 uninitialized 상태로 미리 만들어서 저장한다.

 

세션 초기 설정 (initialization)

app.get('/', function(req, res){
    sess = req.session;
});

라우터 콜백함수 안에서 req.session으로 세션에 접근 할 수 있다.

 

세션 변수 설정

app.get('/login', function(req, res){
    sess = req.session;
    sess.username = "velopert"
});

따로 키를 추가해줄 필요 없이 sess.[키 이름]=값 으로 세션변수 설정이 가능하다.

 

세션 변수 사용

app.get('/', function(req, res){
    sess = req.session;
    console.log(sess.username);
});

세션 변수는 위와같이 하면 사용이 가능하다

 

세션 제거

req.session.destroy(function(err){
   // cannot access session here
});

세션을 제거할때는 destroy메소드를 사용한다.

destroy()의 콜백함수에서는 세션에 접근 할 수 없다.

이제 세션을 Rest API에 하나씩 적용해보자.

 

LOGIN API

    app.get('/login/:username/:password', function(req, res){
        var sess;
        sess = req.session;

        fs.readFile(__dirname + "/../data/user.json", "utf8", function(err, data){
            var users = JSON.parse(data);
            var username = req.params.username;
            var password = req.params.password;
            var result = {};
            if(!users[username]){
                // USERNAME NOT FOUND
                result["success"] = 0;
                result["error"] = "not found";
                res.json(result);
                return;
            }
            if(users[username]["password"] == password){
                result["success"] = 1;
                sess.username = username;
                sess.name = users[username]["name"];
                res.json(result);
            }else{
                result["success"] = 0;
                result["error"] = "incorrect";
                res.json(result);
            }
        })
    })

(router/main.js에 추가)

로그인에 성공했을때 세션에 username과 name을 저장하게 했다.

 

LOGOUT API

로그아웃을 위해서 router/main.js에 추가하자.

	app.get('/logout', function(req, res){
        sess = req.session;
        if(sess.username){
            req.session.destroy(function(err){
                if(err){
                    console.log(err);
                }else{
                    res.redirect('/');
                }
            })
        }else{
            res.redirect('/');
        }
    })

로그아웃하면 메인페이지로 redirect된다

 

메인페이지 수정

메인페이지에서 세션을 조회 할 수 있게 수정해주자

라우터 상단의 app.get('/'...)부분을 업데이트해주자

	app.get('/',function(req,res){
         var sess = req.session;


         res.render('index', {
             title: "MY HOMEPAGE",
             length: 5,
             name: sess.name,
             username: sess.username
         })
     });

세션 변수가 EJS 템플릿에 전달된다

이제 EJS 파일을 수정해주자

 

view/body.ejs

<h1>Loop it!</h1>
<ul>
    <% for(var i=0; i<length; i++){ %>
        <li>
            <%= "LOOP" + i %>
        </li>
    <% } %>
</ul>

<% if(username){ %>
    <h2>Welcome! <%= username %> (name: <%= name %>)</h2>
<% }else{ %>
    <h2> Please Login. </h2>
<% } %>

로그인 되어있는지 체크하고 로그인중이면 환연메시지를.

그렇지 않다면 로그인 하라는 메시지를 띄운다.

 

테스트

1. http://localhost:3000/ 접속

2. http://localhost:3000/user/pass 접속 (유저 데이터는 본인 데이터로 입력하면 된다)

3. http://localhost:3000/ 재접속

4. http://localhost:3000/logout 접속

 


WRITTEN BY
Clasha

,

RESTful API

REST 는 Representational State Transfer 의 약자로서,  월드와이드웹(www) 와 같은 하이퍼미디어 시스템을 위한 소프트웨어 아키텍쳐 중 하나의 형식이다. REST 서버는 클라이언트로 하여금 HTTP 프로토콜을 사용해 서버의 정보에 접근 및 변경을 가능케 하며. 여기서 정보는 text, xml, json 등 형식으로 제공되는데, 제일 많이 사용되는 방식은 JSON이다.

 

HTTP 메소드

HTTP/1.1에서 제공되는 메소드는 여러개가 있다.

1. GET - 조회

2 PUT - 생성 및 업데이트

3. DELETE - 제거

4. POST - 생성

여기서 POST와 PUT이 둘다 생성을 해주는 역할을 하는데

어떤 상황에 무얼 써야 할지 헷갈리면 (https://1ambda.github.io/javascripts/rest-api-put-vs-post/)를 참고하자.

 

데이터 생성

JSON기반의 사용자 데이터를 만들어보자.

data폴더를 만들고 그 안에 user.json파일을 생성해준다

{
    "first_user": {
        "password": "first_pass",
        "name": "abet"
    },
    "second_user":{
        "password": "second_pass",
        "name": "betty"
    }
}

 

첫번째 API: GET/list

모든 유저 리스트를 출력하는 GET API를 작성하자.

user.json파일을 읽어오기 위해서 fs모듈을 사용한다.

module.exports = function(app, fs)
{

     app.get('/',function(req,res){
         res.render('index', {
             title: "MY HOMEPAGE",
             length: 5
         })
     });



    app.get('/list', function (req, res) {
       fs.readFile( __dirname + "/../data/" + "user.json", 'utf8', function (err, data) {
           console.log( data );
           res.end( data );
       });
    })


}

___dirname은 현재 모듈의 위치를 나타내준다.

router모듈은 router폴더에 들어있으니, data폴더에 접근하기 위해서

/../를 붙여줘서 상위폴더에 먼저 접근해준다.

 

그리고 서버를 실행해서 http://localhost/list에 접속해준다.

 

두번째 API: GET/getUser/:username

이번에는 특정 유저 username의 디테일한 정보를 가져오는 GET API를 작성해보도록 하자.

다음 코드를 router/main.js의 list API 아래에 작성해주자.

app.get('/getUser/:username', function(req, res){
       fs.readFile( __dirname + "/../data/user.json", 'utf8', function (err, data) {
            var users = JSON.parse(data);
            res.json(users[req.params.username]);
       });
    });

파일을 읽은후. 유저 아이디를 찾아서 출력해준다.

유저를 찾으면 데이터를 출력하고 없다면 []를 출력한다.

fs.readFile()로 파일을 읽었을 때는 텍스트 형태로 읽어지기 때문에, JSON.parse()를 해야한다.

서버를 재실행해주고 http://localhost:3000/getUser/first_user에 접속해보자.

 

세번째 API: POST addUser/:username

이번 API는 POST를 사용해서 만들거다.

편한 테스팅을 위하여 크롬 확장프로그램인 Postman 을 사용하겠습니다.

HTTP 패킷을 요청하고 분석 할 수 있는 툴이다. 비슷한 프로그램이 이미 설치되있는사람들은 생략해도 된다.

 

router/main.js의 getUser API 하단에 작성해주자.

app.post('/addUser/:username', function(req, res){

        var result = {  };
        var username = req.params.username;

        // CHECK REQ VALIDITY
        if(!req.body["password"] || !req.body["name"]){
            result["success"] = 0;
            result["error"] = "invalid request";
            res.json(result);
            return;
        }

        // LOAD DATA & CHECK DUPLICATION
        fs.readFile( __dirname + "/../data/user.json", 'utf8',  function(err, data){
            var users = JSON.parse(data);
            if(users[username]){
                // DUPLICATION FOUND
                result["success"] = 0;
                result["error"] = "duplicate";
                res.json(result);
                return;
            }

            // ADD TO DATA
            users[username] = req.body;

            // SAVE DATA
            fs.writeFile(__dirname + "/../data/user.json",
                         JSON.stringify(users, null, '\t'), "utf8", function(err, data){
                result = {"success": 1};
                res.json(result);
            })
        })
    });
   

JSON 형태가 INVALID 하다면 오류를 반환하고, VALID 하다면 파일을 열어서 username의 중복성을 확인 후
JSON 데이터에 추가하여 다시 저장합니다. stringify(users, null, 2) 은 JSON 의 pretty-print 를 위함 입니다

body 에서 Content-type를 JSON 으로 하셔야 정상적으로 처리됩니다.

 

네번째 API: PUT updateUser/:username

body:{"password":"______","name":"_______"}

이 API는 위의 API와 비슷하다. 사용자 정보를 업데이트 해주는 API이고, PUT메소드를 사용한다.

PUT API 는 idempotent 해야한다. 쉽게말하자면 즉 요청을 몇번 수행하더라도, 같은 결과를 보장해야한다는 이야기다.

마지막 API: DELETE deleteUser/:username

유저를 데이터에서 삭제하는 API다. DELETE메소드를 사용한다.

app.delete('/deleteUser/:username', function(req, res){
        var result = { };
        //LOAD DATA
        fs.readFile(__dirname + "/../data/user.json", "utf8", function(err, data){
            var users = JSON.parse(data);

            // IF NOT FOUND
            if(!users[req.params.username]){
                result["success"] = 0;
                result["error"] = "not found";
                res.json(result);
                return;
            }

            delete users[req.params.username];
            fs.writeFile(__dirname + "/../data/user.json",
                         JSON.stringify(users, null, '\t'), "utf8", function(err, data){
                result["success"] = 1;
                res.json(result);
                return;
            })
        })

    })

router/main.js 전체코드

module.exports = function(app, fs)
{

     app.get('/',function(req,res){
         res.render('index', {
             title: "MY HOMEPAGE",
             length: 5
         })
     });

    app.get('/list', function (req, res) {
       fs.readFile( __dirname + "/../data/user.json", 'utf8', function (err, data) {
           console.log( data );
           res.end( data );
       });
    });

    app.get('/getUser/:username', function(req, res){
       fs.readFile( __dirname + "/../data/user.json", 'utf8', function (err, data) {
            var users = JSON.parse(data);
            res.json(users[req.params.username]);
       });
    });

    app.post('/addUser/:username', function(req, res){

        var result = {  };
        var username = req.params.username;

        // CHECK REQ VALIDITY
        if(!req.body["password"] || !req.body["name"]){
            result["success"] = 0;
            result["error"] = "invalid request";
            res.json(result);
            return;
        }

        // LOAD DATA & CHECK DUPLICATION
        fs.readFile( __dirname + "/../data/user.json", 'utf8',  function(err, data){
            var users = JSON.parse(data);
            if(users[username]){
                // DUPLICATION FOUND
                result["success"] = 0;
                result["error"] = "duplicate";
                res.json(result);
                return;
            }

            // ADD TO DATA
            users[username] = req.body;

            // SAVE DATA
            fs.writeFile(__dirname + "/../data/user.json",
                         JSON.stringify(users, null, '\t'), "utf8", function(err, data){
                result = {"success": 1};
                res.json(result);
            })
        })
    });


    app.put('/updateUser/:username', function(req, res){

        var result = {  };
        var username = req.params.username;

        // CHECK REQ VALIDITY
        if(!req.body["password"] || !req.body["name"]){
            result["success"] = 0;
            result["error"] = "invalid request";
            res.json(result);
            return;
        }

        // LOAD DATA
        fs.readFile( __dirname + "/../data/user.json", 'utf8',  function(err, data){
            var users = JSON.parse(data);
            // ADD/MODIFY DATA
            users[username] = req.body;

            // SAVE DATA
            fs.writeFile(__dirname + "/../data/user.json",
                         JSON.stringify(users, null, '\t'), "utf8", function(err, data){
                result = {"success": 1};
                res.json(result);
            })
        })
    });


    app.delete('/deleteUser/:username', function(req, res){
        var result = { };
        //LOAD DATA
        fs.readFile(__dirname + "/../data/user.json", "utf8", function(err, data){
            var users = JSON.parse(data);

            // IF NOT FOUND
            if(!users[req.params.username]){
                result["success"] = 0;
                result["error"] = "not found";
                res.json(result);
                return;
            }

            // DELETE FROM DATA
            delete users[req.params.username];

            // SAVE FILE
            fs.writeFile(__dirname + "/../data/user.json",
                         JSON.stringify(users, null, '\t'), "utf8", function(err, data){
                result["success"] = 1;
                res.json(result);
                return;
            })
        })

    })

}

'node JS' 카테고리의 다른 글

9.Node js mongo DB와 Express를 사용해 Restful API 만들기-1  (0) 2020.01.31
8.Node js Express-4(Express-session)  (0) 2020.01.25
6.Node js Express-2(EJS)  (0) 2020.01.11
5.Node js Express-1  (0) 2020.01.04
4.Http Module  (0) 2019.12.26

WRITTEN BY
Clasha

,