Express.js란 무엇인가?

등록일: 2014. 08. 20

풀스택 자바스크립트 개발: 실습 중심의 모던 웹 애플리케이션 프로그래밍

  • 아자트 마르다노프 지음
  • 이대엽 옮김
  • 520쪽
  • 35,000원
  • 2014년 07월 17일

1장 Express.js란 무엇인가?

Express.js는 Node.js의 핵심 모듈인 httpConnect 컴포넌트를 기반으로 하는 웹 프레임워크다. 그러한 컴포넌트를 미들웨어(middleware)라고 하며, 설정보다는 관례(convention over configuration)와 같은 프레임워크의 철학을 지탱하는 주춧돌에 해당한다. 즉, 개발자들은 특정 프로젝트에 필요한 라이브러리를 어떤 것이든 자유롭게 선택할 수 있으며, 이는 개발자들에게 유연함과 수준 높은 맞춤식 구성을 보장한다.

Node.js의 핵심 모듈만 이용해서 중요 앱을 작성한다면 다음과 같은 비슷한 작업을 위해 동일한 코드를 지속적으로 작성함으로써 바퀴를 재발명할 가능성이 높다.

  • HTTP 요청 본문 파싱
  • 쿠키 파싱
  • 세션 관리
  • URL 경로와 HTTP 요청 메서드를 기반으로 한 복잡한 if 조건을 통해 라우팅을 구성
  • 데이터 타입을 토대로 한 적절한 응답 헤더 결정

Express.js는 이러한 문제를 비롯해 다른 여러 문제를 해결함과 동시에 웹 앱에 MVC 형태의 구조를 제공한다. 이 같은 앱은 백엔드만 갖춘 REST API에서부터 온갖 기능을 제공하는 고도로 확장 가능한 풀스택(jade-browserSocket.IO를 포함하는) 실시간 웹 앱에 이르기까지 다양하다.

루비에 익숙한 일부 개발자들은 Express.js를 루비 온 레일스 프레임워크에 굉장히 다른 방식으로 접근하는 시나트라(Sinatra)와 비교하기도 한다.

2장 Express.js의 작동 방식

보통 Express.js에는 메인 파일이라고 하는 진입점이 있다. 메인 파일에서는 다음과 같은 단계를 밟는다.

  1. 컨트롤러, 유틸리티, 도우미, 모델과 같은 자체적인 모듈을 비롯한 서드파티 의존 모듈을 인클루드한다.
  2. 템플릿 엔진과 해당 템플릿 엔진의 파일 확장자와 같은 Express.js 앱 설정을 구성한다.
  3. 오류 핸들러, 정적 파일 폴더, 쿠키 및 기타 파서와 같은 미들웨어를 정의한다.
  4. 라우팅을 정의한다.
  5. MongoDB, Redis 또는 MySQL과 같은 데이터베이스에 연결한다.
  6. 앱을 구동한다.

고급 설정에서는 앱을 모듈로 공개하거나 up-time 모듈을 사용해야 할지도 모른다. 그뿐만 아니라 최근에 추가된 클러스터를 활용해('팁과 트릭'에서 설명하는 것처럼) 다수의 워커를 생성할 수도 있다.

up-time 모듈에 관해서는 "Node.js 계속 구동시키기"를 참고한다.

Express.js 앱이 실행되면 Express.js가 요청을 대기한다. 앱으로 들어오는 각 요청은 정의된 미들웨어와 라우팅에 따라 맨 위에서 시작해 맨 아래까지 처리된다. 이러한 측면은 실행 흐름을 제어하는 데 중요하다. 이를테면, 각 요청을 여러 개의 함수가 처리하게 할 수도 있다. 그러한 함수 중 일부는 중간에 위치할 것이며, 그래서 이름도 미들웨어다.

  1. 쿠키 정보를 파싱하고, 파싱이 완료되면 다음 단계로 이동한다.
  2. URL로부터 매개변수를 파싱하고, 파싱이 완료되면 다음 단계로 이동한다.
  3. 사용자가 인증되면(쿠키/세션) 매개변수의 값을 토대로 데이터베이스에서 정보를 가져와 일치하는 것이 있으면 다음 단계로 이동한다.
  4. 데이터를 표시하고 응답을 마친다.

3장 설치

Express.js는 두 가지 버전이 있다.

  1. 스캐폴딩을 위한 명령줄 도구
  2. Node.js 앱 안의 모듈(예: 실제 의존 모듈)

명령줄 도구를 설치하려면 맥/리눅스 장비의 아무 곳에서나 $ npm install -g express를 실행한다. 이렇게 하면 도구를 내려받아 $ express 터미널 명령어를 적절한 경로로 링크해서 나중에 새로운 앱을 만들 때 CLI에 접근할 수 있다.

물론 $ npm install -g [email protected]와 같이 NPM을 통해 좀 더 구체적으로 3.3.5 버전을 설치할 수도 있다.

참고

대부분의 경우 시스템에서 폴더에 쓰기 위한 루트/관리자 권한이 필요할 것이다. 이러한 경우 $ sudo npm install -g express를 실행하면 된다.

npm-g
그림 3.1 NPM에 -g와 $ express -V를 지정해서 실행한 결과

참고로 위 그림에서 지정한 경로는 /usr/local/lib/node_modules/express다.

이번에는 Node.js 앱 안의 모듈(예: 의존 모듈로 로컬 Express.js 모듈을 설치하는 경우)을 설치하기 위해 $ mkdir expressjsguide로 새 폴더를 만들자. 이 폴더는 이 책에서 사용할 프로젝트 폴더가 된다. 이제 $ cd expressjsguide로 해당 폴더로 이동할 수 있다.

프로젝트 폴더로 이동하고 나면 직접 텍스트 편집기에서package.json을 만들거나 $ npm init 터미널 명령어를 실행해서 만들 수 있다.

npm-init
그림 3.2 $ npm init을 실행한 결과

$ npm init만 실행했을 때 만들어지는 package.json 파일의 예는 다음과 같다.

{
  "name": "expressjsguide",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "BSD"
}

마지막으로 NPM을 활용하는 모듈을 설치하면 된다.

$ npm install express

또는 좀 더 구체적으로 지정하고 싶다면 된다(더 나은 방법이다).

$ npm install [email protected]

참고

package.json 파일이나 node_modules 폴더가 없는 상태에서 앞에서 언급한 $ npm install express 명령어를 실행하면 똑똑한 NPM이 디렉터리 트리를 탐색해서 package.json 파일이나 node_modules 중 하나가 들어 있는 폴더를 찾는다. 이러한 동작 방식은 Git의 로직을 다소 흉내 낸 것이다. NPM 설치 알고리즘에 관한 더 자세한 사항은 공식 문서를 참고한다.

아니면 package.json 파일에 의존 모듈("express": "3.3.x""express": "3.3.5")을 지정해서 업데이트한 후 $ npm install을 실행하면 된다.

Express.js v3.3.5 의존 모듈이 추가된 package.json 파일은 다음과 같다.

{
  "name": "expressjsguide",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "3.3.5"
  },
  "author": "",
  "license": "BSD"
}
$ npm install

다음 그림에서 [email protected] 문자열 다음에 나오는 경로를 눈여겨보자.

npm-install
그림 3.3 $ npm install을 실행한 결과

기존 프로젝트에 Express.js를 설치해서 package.json 파일(이미 해당 프로젝트 폴더에 존재하는)에 의존 모듈을 저장하고 싶다면(이렇게 하는 것이 현명하다!) $ npm install express --save를 실행한다.

Express.js와 관련 의존 모듈이 설치됐는지 검사하려면 $ npm ls 명령어를 실행하면 된다.

npm-ls
그림 3.4 $ npm ls를 실행한 결과

4장 헬로 월드 예제

4장에서는 첫걸음을 떼기 위해 필수 프로그래밍 예제인 "헬로 월드(Hello World)" 앱을 만들겠다. 온라인 튜토리얼을 통해 이미 헬로 월드 앱을 만들어봤다면 2부(인터페이스)로 곧바로 건너뛰어도 된다.

expressjsguide 폴더에서 hello.js 파일을 만든다. VIM이나 이맥스(Emacs), 서브라임 텍스트 2(Sublime Text), 텍스트메이트(TextMate)처럼 즐겨쓰는 텍스트 편집기를 이용한다. hello.js 서버 파일에서는 Express.js를 활용할 것이므로 다음 라이브러리를 인클루드한다.

var express = require('express');

이제 애플리케이션을 생성한다.

var app = express();

애플리케이션은 로컬의 3000번 포트에서 동작하는 웹 서버다.

var port = 3000;

app.get() 함수를 이용해 와일드카드 라우팅(*)을 정의한다.

app.get('*', function(req, res){
  res.end('Hello World');
});

위의 app.get() 함수는 문자열 형식으로 URL 패턴에 대한 정규 표현식을 받는다. 위 예제에서는 와일드카드 * 문자를 통해 모든 URL을 처리한다.

app.get()의 두 번째 매개변수는 요청 핸들러(request handler)다. 일반적인 Express.js 요청 핸들러는 Node.js의 네이티브 메서드이자 핵심 메서드인 http.createServer()에 콜백으로 전달하는 것과 비슷하다.

http 모듈에 익숙하지 않은 분들을 위해 설명하자면 요청 핸들러는 서버가 특정 요청을 받을 때마다 실행되는 함수다. 여기서 특정 요청이란 보통 HTTP 메서드(예: GET), URL 경로(예: 프로토콜이 지정되지 않은 URL), 호스트와 포트로 정의된다.

Express.js 요청 핸들러에는 최소한 두 개의 매개변수(더 자세한 사항은 후반부의 "오류 처리"를 참고한다)를 필요로 하는데, 요청(간단히 req)과 응답(간단히 res)이 여기에 해당한다. 이와 비슷하게 res.pipe()와/또는 res.on('data', function(chunk) {...})를 통해 읽기 가능 및 쓰기 가능한 스트림 인터페이스를 활용할 수 있다.

마지막으로 Express.js 웹 서버를 구동하고 콜백에서 사용자 친화적인 터미널 메시지를 출력해보자.

app.listen(port, function(){
  console.log('The server is running, ' +
    ' please, open your browser at http://localhost:%s',
    port);
});

스크립트를 실행하려면 프로젝트 폴더에서 $ node hello.js를 실행한다.

node-hello
그림 4.1 $ node hello.js를 실행한 결과

이제 브라우저에서 http://localhost:3000(http://127.0.0.1:3000이나 http://0.0.0.0:3000/과 같다)로 들어가 보면 URL 경로와 상관없이 "Hello World"라는 메시지가 표시될 것이다.

hello
그림 4.2 브라우저에서 http://localhost:3000/ads를 연 모습

참고로 hello.js의 전체 코드는 깃허브 저장소에서도 확인할 수 있다.

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

app.get('*', function(req, res){
    res.end('Hello World');
});

app.listen(port, function(){
    console.log('The server is running, ' +
    ' please open your browser at http://localhost:%s',
        port);
});

"Hello" 문구와 함께 서버로 전달한 이름을 되돌려줌으로써 예제를 좀 더 상호작용이 풍부하게끔 수정할 수 있다. 그러자면 cp hello.js hello-name.js 명령어로 hello.js 파일을 복사한 다음 이전 예제에서 모든 것을 포괄하는 라우팅 앞에 다음과 같은 라우팅을 추가하면 된다.

app.get('/name/:user_name', function(req,res) {
  res.status(200);
  res.set('Content-type', 'text/html');
  res.send('<html><body>' +
    '<h1>Hello ' + req.params.user_name + '</h1>' +
    '</body></html>'
  );
});

/name/:user_name 라우팅 안에서는 적절한 HTTP 상태 코드(200은 '문제 없음'을 의미한다)와 HTTP 응답 헤더를 설정한 다음 동적 텍스트를 HTML body 태그와 h1 태그로 감쌌다.

참고

res.send()는 우리의 오랜 친구인 http 모듈의 res.end()가 하던 일보다 더 많은 일을 편리하게 해주는 특별한 Express.js 메서드다. 예를 들어, res.send() 메서드는 자동으로 Content-Length HTTP 헤더를 추가한다. 그뿐만 아니라 제공하는 데이터를 토대로 Content-Type 인자를 지정하기도 한다. 더 자세한 사항은 후반부의 "응답(Response)" 장을 참고한다.

hello-name.js 파일의 전체 소스코드 또한 깃허브 저장소에서 확인할 수 있다.

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

app.get('/name/:user_name', function(req,res) {
  res.status(200);
  res.set('Content-type', 'text/html');
  res.end('<html><body>' +
    '<h1>Hello ' + req.params.user_name + '</h1>' +
    '</body></html>'
  );
});

app.get('*', function(req, res){
  res.end('Hello World');
});

app.listen(port, function(){
  console.log('The server is running, ' +
    ' please open your browser at http://localhost:%s',
     port);
});

이전 서버를 종료했다가 hello-name.js 스크립트를 실행하고 나면 브라우저에서 http://localhost:3000/name/azat와 같은 식으로 들어갔을 때 다음과 같은 동적 응답을 볼 수 있다.

hello-name
그림 4.3 역동적인 헬로 사용자 예제