Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

express.js 4의 변경 사항

express.js는 Node.js에서 킬러 웹 프레임워크고 초창기부터 계속 사용하고 있다. 참고로 최근 npm 사이트에서 고심끝에 hapi를 쓰기로 결정한 것으로 보아 hapi는 안 써봤지만 최근에 인기를 많이 끌고 있는 듯하다. 어쨌든 express.js지난 4월 4.0으로 메이저 버전을 올리면서 또 한 번의 큰 변화가 이뤄졌다. 릴리즈 된 지 좀 됐기 때문에 현재 최신 버전은 4.4.3이다. 여러 부분에서 좋은 방향으로 바뀌었기 때문에 express 3.x를 쓰고 있다면 4.x로의 업그레이드를 고려해 볼 만하다. 공식 문서로는 Migrating from 3.x to 4.xNew features in 4.x가 있고 이 글도 이 두 문서에 바탕을 두고 있다.

express 4

express 4에서 가장 큰 변경사항 중 하나는 Connect와의 의존성이 사라졌다는 것이다. Connect는 미들웨어 프레임워크로 express는 Connect에 기반을 두고 만들어졌기 때문에 그동안 성능에 대한 이슈제기가 계속 있었고(아무래도 Connect 위에 계층이 하나 더 올라가 있으므로...) Connect를 직접 사용하는 것을 추천하는 내용도 많이 있었다. Express 4 in production라는 글에 따르면 응답시간이 20%, 메모리 소비가 10% 줄었다는 얘기도 있다.

4.x부터는 Connect를 직접 사용하지 않게 바뀌어서 package.json에서도 connect가 완전히 빠졌다. Connect가 빠지면서 기존에 express에 내장되어 있던 connect 미들웨어도 모두 기본에서 제외됐다. 그래서 static 미들웨어를 제외한 body-parser, cookie-parser등을 별도로 설치하지 않으면 사용할 수 없다. 그 외 미들웨어는 Connect의 README에서 확인할 수 있고 필요한 미들웨어를 가져다가 설치할 수 있다. 이 변경사항으로 express가 다른 미들웨어의 버전 업그레이드를 신경 쓰지 않고 별도로 운영할 수 있게 된 것이므로 좋은 선택이라고 본다. 공식 미들웨어들은 이제 express.js에서 직접 관리를 하고 있다.

Router

express.js를 사용하고 있다면 Router객체의 추가가 가장 피부로 느껴지는 개선점일 것이다. 그동안 라우팅 처리에 불편한 부분이 꽤 있었다면 4.x의 Router 객체로 문제 대부분이 해결되고 사용하기 편해졌다. Router 객체는 작은 express 앱과 비슷하게 생각해도 무방하다.

var express = require('express'),
    router = express.Router();

router.use(function(req, res, next) {});

router.get('/', function(req, res, next) {});

module.exports = router;

express.Router()를 이용해서 Router 객체를 생성할 수 있고 이 객체에 미들웨어나 라우팅 설정을 추가할 수 있다. 이는 이전에 라우팅마다 일일이 처리하는 부분 없이 기능이나 로직별로 라우팅을 나눠서 관리할 수 있다는 의미이다. 즉, user 라우터에는 인증 미들웨어를 모두 추가하고 api 라우터에는 별도의 로거를 추가하는 등의 행위를 쉽게 할 수 있다는 의미이다. 그리고 Router를 app.use('/api', require('./router');처럼 연결할 수 있어서 Router 전체에 대한 URL 접미사를 한꺼번에 관리하는 것도 가능하다.

route()함수는 Router 객체를 반환하므로 HTTP Verb 별로 설정할 필요없이 다음과 같이 사용할 수 있다. RESTful URL을 설계해서 사용한다면 같은 리소스에 다양한 method를 설정해서 사용하는 경우가 많으므로 이럴 때 유용하다.

router.route('/users/:user_id')
  .get(function(req, res, next) {})
  .put(function(req, res, next) {})
  .post(function(req, res, next) {})
  .delete(function(req, res, next) {})

all()함수를 사용하면 모든 해당 URL의 모든 method에 미들웨어를 적용할 수 있다.

router.all('/users', requireAuthentication);
// or
router.route('/users/:user_id')
  .all(requireAuthentication);
  .get(function(req, res, next) {});


app.configure()

3.x에서 환경별 설정을 하기 위해서 사용하던 app.configure()를 더는 지원하지 않는다. 그래서 기존에는 app.configure('development', function() {});와 같이 환경별 설정을 추가했지만 4.x부터는 다음과 같이 직접 if문을 사용해야 한다.

var env = process.env.NODE_ENV || 'development';
if ('development' == env) {
   // 개발환경용 설정
}


app.router

app.use(app.router)와 같이 사용할 때의 app.router를 얘기하는데 이 부분도 제거되었다. app.router를 아는 사람도 있고 모르는 사람도 있는데(나도 명시적으로 사용하지 않아서 얼마 전에 알게 된 부분이다.) app.router는 라우팅 미들웨어를 사용하도록 하는 코드이다. 이 코드를 생략할 경우 express가 자동으로 추가하기 때문에 생략하면 설정 마지막에 자동으로 추가해 줘서 사용하지 않아도(이 코드를 처음 보더라도) 실제로는 사용하고 있는 부분이다. 이 코드가 필요한 이유는 라우팅 처리를 어느 단계에서 처리할 것인가를 지정하기 위해서이다. 라우팅이후에 실행되어야 하는 미들웨어가 있는다든지 정적 리소스 URL과 URL이 겹쳐서 명시적으로 우선순위를 주고 싶다든지 할 때 사용할 수 있다.

app.use(cookieParser());
app.use(bodyParser());
// app.use(app.router); 이 코드를 더이상 사용하지 않는다.

app.get('/' ...);
app.post(...);

// middleware
app.use(function(req, res, next);

그래서 4.x에서는 위와 같이 사용하고 app.use()와 라우팅이 선언한 순서대로 실행된다. 그래서 모든 라우팅이 실행한 뒤에 사용할 미들웨어(오류 처리 등)는 위 코드처럼 라우팅 코드 이후에 호출해야 한다.

Connect의 패치 코드

Coonect는 Node의 객체를 프로토타입으로 확장해서 사용하고 있었는데 이는 좋은 방법이 아니라서 Connect 3에서도 제거되었다. Connect는 물론이고 Express에서도 더이상 이러한 코드는 사용하지 않는다. 예를 들면 res.on('header')res.charset, res.headerSent(Node에 res.headersSent가 있다.) 등이 있다. 여기서 res.charset의 경우 캐릭터셋을 설정하고 싶다면 res.set('content-type')res.type()를 사용해서 설정해야 하고 res.setHeader()에서는 기본 캐릭터셋을 추가하지 않는다.

그 밖의 변경사항

  • app.use에서 동적 파라미터를 받을 수 있게 되어서 app.use('/users/:id', function(req, res, next) {});처럼 사용할 수 있다. 라우터처럼 동적 파라미터는 req.params.id로 접근할 수 있다.
  • req.accepted()req.accepts()로 바뀌었다. req.accepts(), req.acceptsEncodings(), req.acceptsCharsets(), req.acceptsLanguages()가 있지만, 내부적으로 결국 accepts를 사용한다.
  • res.location()에서 상대 URL을 처리하지 않는다. 상대 URL은 브라우저에서 처리한다.
  • app.mountpath로 express 앱을 다른 express 앱에 마운팅할 수 있다.
  • req.params이 배열이 아니라 객체다.
  • res.locals는 객체가 아니라 함수다.
  • 콘텐츠 타입을 다루는 req.is는 내부적으로 type-is를 사용한다.
2014/06/22 23:19 2014/06/22 23:19