관리 메뉴

nkdk의 세상

node.js express node.js 웹 프레임워크? 파헤치기 본문

My Programing/Node.js

node.js express node.js 웹 프레임워크? 파헤치기

nkdk 2012. 4. 5. 19:21

안녕하세요. express 웹 기본 프레임 워크 설명에 들어갑니다. 일단..

저번에 올린 자료를 참고 하셔서 설치를 완료 하셨으면..


설명에 들어갑니다.

app.js 를 보시면.. 

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

app.get('/', function(req, res){

  res.send('hello world');

});

app.listen(3000);


이런 부분이 있는데요. 이 부분은 서버를 만든 부분입니다.

https 로 만드는 것도 가능한데요. 

http://nodejs.org/docs/v0.3.7/api/https.html#https.createServer 여길 참조 해 보세요.


그 다음은 설정 부분입니다.

app.configure(function(){

    app.use(express.methodOverride());

    app.use(express.bodyParser());

    app.use(app.router);

});

app.configure('development', function(){

    app.use(express.static(__dirname + '/public'));

    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

});

app.configure('production', function(){

  var oneYear = 31557600000;

  app.use(express.static(__dirname + '/public', { maxAge: oneYear }));

  app.use(express.errorHandler());

});

보시면 아시겠지만 개발자 환경, 운영환경 이렇게 2개로 나눠서 설정하는 것이 가능합니다.

app.configure('stage', 'prod', function(){

  // config

});

비슷한 환경에서 여러 환경을 문자열로 전달 할 수 있습니다.

 app.configure(function(){

    app.set('views', __dirname + '/views');

    app.set('views');

    // => "/absolute/path/to/views"


    app.enable('some feature');

    // same as app.set('some feature', true);


    app.disable('some feature');

    // same as app.set('some feature', false);


    app.enabled('some feature')

    // => false

 });

이런식으로 enable 및 disable 도 가능합니다.

개발 환경 및 운영 환경을 띄울 때 다음과 같이 구분해서 띄울 수 있습니다.

> NODE_ENV=production node app.js   // 운영 환경을 띄울 경우

개발 환경이냐 아니면 운영 환경 이건 엄청 중요합니다. 왜냐하면 캐싱메커니즘이라던지 여러가지에 영향을 끼치거든요.


Express 에서 지원하는 셋팅

basepath = 응용 프로그램의 기본 경로 res.redirect() 를 사용합니다.

views = 기본적으로 CWD/views 부분에 디렉토리입니다.

view engine = 확장자 없는 렌더링 되는 뷰 엔진을 어떤걸 쓸 것이냐.

view options = object 형 특별한 뷰옵션입니다.

view cache = 프로덕션에서 이네이블 되는 view caching 입니다.

case sensitive routes = case-sensitive 루팅을 이네이블 합니다.

strict routing = 활성화가 되면 더 이상 슬래쉬를 무시하지 않습니다.

jsonp callback Enable =  res.send() / res.json()가 jsonp 의 전송을 지원합니다.


경로(라우팅)에 대한 설명

설정 안에서 라우팅에 대한 규칙을 정의할 수 있습니다.

예를들면 http://localhost:3000/user/500 http://localhost:3000/user/40 이런 것들에 url을 받으려면

app.get('/user/:id', function(req, res){

    res.send('user ' + req.params.id);

});


이런 소스를 app.js 에 추가 시키시고 실행하시면 인식이 됩니다.

그리고 RegEXP를 이용하시면 정규표현으로 선언하는 것이 가능합니다.

\/user\/([^\/]+)\/? 와 같이 추가를 한다던지 , 아래와 같이 추가도 가능합니다.


app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function(req, res){

    res.send(req.params);

});


위에 내용을 넣으면 다음과 같이 테스트가 가능합니다.

> curl http://dev:3000/user

   [null,null]

>  $ curl http://dev:3000/users

   [null,null]

>  $ curl http://dev:3000/users/1

   ["1",null]

>  $ curl http://dev:3000/users/1..15

   ["1","15"]


아래와 같이 라우팅 추가가 가능합니다.

라우팅 인식이 가능한 형식
------------------------------------------
 "/user/:id"

 /user/12


 "/users/:id?"

 /users/5

 /users


 "/files/*"

 /files/jquery.js

 /files/javascripts/jquery.js


 "/file/*.*"

 /files/jquery.js

 /files/javascripts/jquery.js


 "/user/:id/:operation?"

 /user/1

 /user/1/edit


 "/products.:format"

 /products.json

 /products.xml


 "/products.:format?"

 /products.json

 /products.xml

 /products


 "/user/:id.:format?"

 /user/12

 /user/12.json
------------------------------------------


json 이라던지 값을 받을 때 받는 방법에 대해서 설명해 보겠습니다.

bodyParser 라는 것을 중간에서 사용해서 req.body 로 뽑아 내는데요 다음과 같이 사용합니다.

var express = require('express')

  , app = express.createServer();

app.use(express.bodyParser());

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

  res.send(req.body);

});

app.listen(3000);


app.js 에 다 지우고 이 내용을 넣어 주시면 다음과 같이 나옵니다.

> curl http://localhost:3000/ancd=good 

위와 같이 쳐 보세요.


아 그리고 라우팅 규칙에서 다음과 같은 것도 가능합니다.

‘/user/:id([0-9]+)’

이 규칙은 유저 아이디가 숫자로만 가능하다고 선언해 주는 것입니다.


경로(라우팅) 규칙을 전달하는 규칙

매칭이 되는 경우와 매칭이 되지 않는 경우를 판별하는 것인데요. 다음과 같이 가능합니다.

app.get('/users/:id?', function(req, res, next){

    var id = req.params.id;

    if (id) {

        // do something

    } else {

        next();

    }

});

app.get('/users', function(req, res){

    // do something else

});


즉 req.params.id 가 있는 경우에는 뭔가를 실행하고요. 없는 경우에는 다음으로 넘어가는 next() 를 실행합니다.


app.all 이라는 것도 존재 하는데요. 다음과 같이 사용합니다.

var express = require('express')

  , app = express.createServer();


var users = [{ name: 'nkdk' }];


app.all('/user/:id/:op?', function(req, res, next){

  req.user = users[req.params.id];

  if (req.user) {

    next();

  } else {

    next(new Error('cannot find user ' + req.params.id));

  }

});


app.get('/user/:id', function(req, res){

  res.send('viewing ' + req.user.name);

});


app.get('/user/:id/edit', function(req, res){

  res.send('editing ' + req.user.name);

});


app.put('/user/:id', function(req, res){

  res.send('updating ' + req.user.name);

});


app.get('*', function(req, res){

  res.send('what???', 404);

});


app.listen(3000); 


다음과 같이 넣고.. 

> curl http://localhost:3000/user

> curl http://localhost:3000/user/0

> curl http://localhost:3000/user/0/edit


하시면 각각 결과가 다릅니다.


미들웨어

접속했을 당시에 미들웨어(미들 처리?)를 선언하는 것이 가능합니다. 선언은 간단합니다.

var express = require('express');

var app = express.createServer(

      express.logger()

    , express.bodyParser()

  );


app.use(express.logger({ format: ':method :url' }));

이렇게 넣어 놓으면 node node.js 실행하실때 url 로그를 남깁니다. 접속시에..

x.xx.xx.x - - [Thu, 05 Apr 2012 07:04:18 GMT] "GET /user/0 HTTP/1.1" 200 12 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.10xx.142 Safari/535.19"


이런식으로 로그를 남기네요.

표준적으로는

var connect = require('connect');

app.use(connect.logger());

app.use(connect.bodyParser());

이렇게도 가능한데요.

app.use 와 같이.. 중간에 미들웨어를 넣는 것도 가능합니다.


종류는 다음과 같습니다.

app.use(express.logger(...));
app.use(express.bodyParser(...));
app.use(express.cookieParser(...));
app.use(express.session(...));
app.use(app.router);
app.use(express.static(...));
app.use(express.errorHandler(...));


사용법은 아래와 같습니다.

var downloads = {};


app.use(app.router);

app.use(express.static(__dirname + '/public'));


app.get('/*', function(req, res, next){

  var file = req.params[0];

  downloads[file] = downloads[file] || 0;

  downloads[file]++;

  next();

});

정말 좋네요. 


경로 관련 미들웨어

경로를 결정할 때에도 여러가지 미들웨어가 필요하다 생각된다. 그 방법은 간단하게 해결 가능합니다.


(잘못된 예)

app.get('/user/:id', function(req, res, next){

  loadUser(req.params.id, function(err, user){

    if (err) return next(err);

    res.send('Viewing user ' + user.name);

  });

});


위에 소스에서 loadUser 라는 부분이 보이네요.

그런데 아무래도 너무 복잡하지 않나요? 안될거 같아요. 저런 코드로는.. 

(잘 된 코드 예)

function loadUser(req, res, next) {

  // You would fetch your user from the db

  var user = users[req.params.id];

  if (user) {

    req.user = user;

    next();

  } else {

    next(new Error('Failed to load user ' + req.params.id));

  }

}

app.get('/user/:id', loadUser, function(req, res){

  res.send('Viewing user ' + req.user.name);

});


다음과 같이 사용하면 loadUser에  req, res, next 도 모두 넘겨 주게 됨으로 인해 코드의 가독성이 올라갑니다. 

DRY 규칙을 성공적으로 완수하는 거죠 :) 


비슷한 예로 접속 가능할지 안할지에 대한 것도 아래와 같이 가능하고요.

function andRestrictToSelf(req, res, next) {

  req.authenticatedUser.id == req.user.id

    ? next()

    : next(new Error('Unauthorized'));

}


app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){

  res.send('Editing user ' + req.user.name);

});


아래와 같이 인수를 넘기는 것도 가능합니다.

function andRestrictTo(role) {

  return function(req, res, next) {

    req.authenticatedUser.role == role

      ? next()

      : next(new Error('Unauthorized'));

  }

}


app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){

  res.send('Deleted user ' + req.user.name);

});


공통적으로 다음과 같이 사용할 수 있습니다.

var a = [middleware1, middleware2]

  , b = [middleware3, middleware4]

  , all = [a, b];


app.get('/foo', a, function(){});

app.get('/bar', a, function(){});


app.get('/', a, middleware3, middleware4, function(){});

app.get('/', a, b, function(){});

app.get('/', all, function(){});


자세한 내용은 샘플이 준비되어 있으니,

https://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js

여기를 참조 하세요.


HTTP Methods

app.get() 이라던지 app.post(), app.del(), 등을 가지고 있는데요. POST를 사용할 때의 일반적인 예제를 보겠습니다.

일단 저의 경우는 /public 이라는 폴더에 post.html 이라는 걸 만들었습니다.

<form method="post" action="/">

  <input type="hidden" name="_method" value="put" />

  <input type="text" name="user[name]" />

  <input type="text" name="user[email]" />

  <input type="submit" value="Submit" />    

</form>


app.js 에는

app.put('/', function(){

    console.log(req.body.user);

    res.redirect('back');

});


다음과 같이 하면 됩니다. 이렇게 하면 콘솔에 name, email 이 나오게 됩니다.

{ name: 'asd', email: '222' }


에러 핸들링

// Error Handling

function NotFound(msg){

  this.name = 'NotFound';

  Error.call(this, msg);

  Error.captureStackTrace(this, arguments.callee);

}

NotFound.prototype.__proto__ = Error.prototype;

app.get('/404', function(req, res){

  throw new NotFound;

});

app.get('/500', function(req, res){

  throw new Error('keyboard cat!');

});

app.error(function(err, req, res, next){

    if (err instanceof NotFound) {

        res.render('404.jade');

    } else {

        next(err);

    }

});

app.error(function(err, req, res){

  res.render('500.jade', {

     error: err

  });

});

app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));


간단히 설명하면, 404와 500 에러가 났을 경우 처리 하는 방법입니다.
/404, /500과 같이 설정도 가능하겠지만.. 실제로 연산할 때 에러가 발생 할 수 있습니다.
해당 에러가 났을 경우 NotFound 의 경우는 404.jade(view) NotFound가 아닌 경우는 next로 가서 500.jade 가 됩니다.
app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
물론 이 부분도 해야 합니다.
이 정도 이지 않을까 생각하네요.

경로 파라미터를 감지를 위한 사전 조건 
경로 파라미터 사전 조건은 크게 
데이터 로딩을 하는 것과 요청 URL을 검증함으로 인해서
사용하는 어플에 가독성을 향상시킵니다. (데이타와 url사용)
예로 /user/:userId 는 다음과 같은걸 할 수 있습니다.

app.get('/user/:userId', function(req, res){
  res.send('user ' + req.params.userId);
});
이런식으로 하면 간단히 파라미터의 값을 가져 올수 있네요.


중요한 뷰의 렌더링 규칙!

뷰의 파일이름은 "<name>.<engine>" 이다. 어떤 엔진인지는 반드시 필요하다. 
예를들면 layout.ejs 이건 반드시 require('ejs') 라는 뷰 시스템을 사용해야 한다.
아마도 방식은 jade와 ejs 방식 2개가 있는 것 같다. 더 있을려나?
일단 jade 방식을 설명하면, 다음과 같습니다.
layout 이 좀 더 큰 의미이고 그 안에 index.jade 가 들어 가는 방식입니다.
layout: flase 를 하면 layout 를 사용하지 않습니다.

일단 뷰 엔진 설정은

app.set('view engine', 'jade');

그리고 get 설정은

app.get('/', function(req, res){

    res.render('index.jade', { title: 'My Site' });

});


/로 들어 오면 index.jade 를 실행하고 title 에는 My Site 라고 넘겨 줍니다.

기본엔진을 설정하면

app.set('view engine', 'jade');

res.render('index'); 이렇게 써도 됩니다. jade 를 빼도 되죠.

그런데 물론 

res.render('another-page.ejs'); 

이것도 가능합니다.

app.set('view options', {

  layout: false

});

이렇게 하면 레이아웃 설정이 false 가 됩니다.

/views/layout.jade 에 사용을 안하게 되는거죠.

그런데 레이아웃 이름을 layout.jade 에서 바꾸고 싶은 경우가 있으시죠?

그 경우는

res.render('page', { layout: 'mylayout.jade' });

이런식으로 mylayout 으로 이름을 바꿔줄 수 있습니다.

res.render('page', { layout: __dirname + '/../../mylayout.jade' });

이런식으로 패스를 바꿀수도 있고요.

ejs 방식에서 좋은 예제가 하나 있습니다. 열고 닫기 태그인데요.

app.set('view options', {

    open: '{{',

    close: '}}'

});

이렇게 설정이 가능합니다.


뷰의 부분만 나오게 하기(view Partials)

뷰에서 작은 부분만을 표시 할 때 사용합니다.

partial('comment', { collection: comments }); 라던지

object 를 넣는 것도 가능합니다.

partial('comment', comments);

다음과 같은 것들을 지원합니다.

firstInCollection: true 이면 첫번째 오브젝트

indexInCollection: 콜렉션의 몇번째인가

lastInCollection: 마지막 컬렉션

collectionLength: 컬력션의 크기


res.partial().를 참고 해서 자세하게 보세요.
하지만 최대한 partial 을 사용하지 않는 걸로 권장하네요. 오버 헤드가 걸린다고 합니다.

뷰의 조회(룩업)
아까전에 언급한 partial을 잠시 이야기 하면
views/user/list.jade 라는 곳에서 partial('edit') 라고 하는 것은
views/user/edit.jade 를 부르게 되어 있다.
그리고 메세지 에 관련한 것은
partial('../messages') 라고 하지만..
views/messages.jade 에 있다고 볼 수 있다.

또한 뷰 시스템은 인덱스 템플릿을 허용합니다. 예를 들면 이렇습니다.
res.render('users'); 라는 것은
views/users.jade 혹은 views/users/index.jade 라는 형태가 됩니다.
만약 당신이 partial('users') 라고 지정했다면 이것 역시 ../users/index 를 찾습니다.

partial(‘index’). 이런 선언은 프로그램에 방해 되니 하지 맙시다.

템플릿 엔진 종류
Haml: haml 구현
Jade: haml.js 다음 버전
EJS: 임베디드 자바스크립트 
CoffeeKup: CoffeeScript 베이스 템플릿
jQuery Templates: 노드를 위함.


세션을 사용해 보자. 쿠키도!
세션과 쿠키를 사용하기 위해선 다음과 같은 설정을 해야 합니다.
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat" }));

connect-redis 와 Redis 를 사용해서 저장소에 저장하여 세션 구현이 가능합니다.
connect-redis = http://github.com/visionmedia/connect-redis
Redis = http://code.google.com/p/redis/

> npm install redis
> npm install connect-redis
이렇게 npm 으로 설치도 가능하네요.

그리고 다음과 같이 해 보세요.

var RedisStore = require('connect-redis')(express);

app.use(express.cookieParser());

app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));

이렇게 하시면..  일단 세션에 저장은 되겠습니다.

간단히 쇼핑카트를 구현한다고 했을 때

var RedisStore = require('connect-redis')(express);

app.use(express.bodyParser());

app.use(express.cookieParser());

app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));


app.post('/add-to-cart', function(req, res){

  // Perhaps we posted several items with a form

  // (use the bodyParser() middleware for this)

  var items = req.body.items;

  req.session.items = items;

  res.redirect('back');

});


app.get('/add-to-cart', function(req, res){

  // When redirected back to GET /add-to-cart

  // we could check req.session.items && req.session.items.length

  // to print out a message

  if (req.session.items && req.session.items.length) {

    req.flash('info', 'You have %s items in your cart', req.session.items.length);

  }

  res.render('shopping-cart');

});


이런 처리가 가능하겠네요.

자세한 내용은 
http://www.senchalabs.org/connect/middleware-session.html
여기를 참조하세요.


Request에 종류
사실 이 부분은 점프하고 싶습니다. 왜냐하면
console.log(req) 하면 다 나오는  것 뿐입니다.
간단히 예를 들면
가지고 오는 법은
req.header(key[, defaultValue])
이런 식으로 가져오시면 되고요.
req.header('Host');
req.header('host');
req.header('Accept', '*/*');
이렇게 가져 오면 값을 가져옵니다.

req.header('Referer');
// => "http://google.com"

req.header('Referrer');
// => "http://google.com"
이런것도 있겠고요.

몇개 중요한 걸 보자면
req.header(key[, defaultValue])
req.accepts(type)
req.is(type)
req.param(name[, default])
req.get(field, param)
req.flash(type[, msg])
req.isXMLHttpRequest

정도가 있겠고요.

Response에 종류
이 부분도 request와 마찬가지로 띄어 넘고 싶네요.
res.header(key[, val])
res.charset
res.contentType(type)
res.attachment([filename])
res.sendfile(path[, options[, callback]])
res.download(file[, filename[, callback[, callback2]]])
res.send(body|status[, headers|status[, status]])
res.json(obj[, headers|status[, status]])
res.redirect(url[, status])
res.cookie(name, val[, options])
res.clearCookie(name[, options])
res.render(view[, options[, fn]])
res.partial(view[, options])
res.local(name[, val])
res.locals(obj)

Server에 값 설정
app.set(name[, val])
app.enable(name)
app.enabled(name)
app.disable(name)
app.disabled(name)
app.configure(env|function[, function])
app.redirect(name, val)
app.error(function)
app.helpers(obj)
app.dynamicHelpers(obj)
app.lookup
app.match
app.mounted(fn)
app.register(ext, exports)
app.listen([port[, host]])

이 부분 들은 설명이 필요 할듯 싶긴 한데요. 하나 하나 하시면서 필요한 부분 찾아 보세요.

이로써 node.js 에 express 웹 프레임워크 설명을 마칩니다. 휴! :)