Front End/Web

[Web Server] Express

Express

Express는 Node.js 환경에서 웹 서버, 또는 API 서버를 제작하기 위해 사용되는 프레임워크이다. Node.js HTTP 모듈로 작성한 서버와 달리 미들웨어를 추가 할 수 있고, 라우터를 제공하기 때문에 서버를 작성하는 측면에서 편의성이 증대되고 효율적이다.

🌟
MERN stack? JavaScript 생태계에서 인기 있는 프레임워크인 MongoDB(데이터베이스), Express(서버), React(SPA), Node.js(런타임)를 지칭하는 말이다.

설치 및 간단 사용법

설치

npm을 이용해 설치 할 수 있다. 만약 package.json에 종속되지 않는 임시 설치를 원한다면 --save커맨드를 추가하면 된다.

npm install express

간단 사용법

const express = require('express'); // express import
const app = express() // app에 express 인스턴스 할당
const port = 3000

app.get('/', (req, res) => { // 메서드와 경로에 따라 라우팅하여 응답
  res.send('Hello World!')
})

app.listen(port, () => { // PORT 지정하여 서버 띄우기
  console.log(`Example app listening on port ${port}`)
})

라우팅

라우팅(Routing)은 메서드와 url로 분기점을 만드는 것을 뜻한다. 클라이언트는 특정한 HTTP 요청 메서드와 함께 서버의 특정 URI로 HTTP 요청을 보낸다. 이런 클라이언트의 요청에 해당하는 Endpoint에 따라 서버가 응답하는 방법을 결정하는 것이다. 기본적인 형식은 instance.METHOD(PATH, HANDLER)이다.

const router = express.Router()

router.get('/lower', (req, res) => { // 요청 메서드가 GET이고 URL이 /lower일 때
  res.send(data);
})

router.post('/lower', (req, res) => { // 요청 메서드가 POST이고 URL이 /lower일 때
  // do something
})

추가로 express.Router를 사용해 라우팅을 할 수도 있다. 이 경우 클래스를 사용해 인스턴스를 생성하며, 이는 미들웨어이자 라우팅 시스템으로서 동작하게끔 할 수 있다.

let express = require('express');
let router = express.Router();

// middleware that is specific to this router
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// define the home page route
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// define the about route
router.get('/about', function(req, res) {
  res.send('About birds');
});

module.exports = router;

// 다른 파일에 import하여 만들어 둔 라우팅 모듈 사용 가능

let birds = require('./birds');
app.use('/birds', birds);

라우트 메서드 METHOD

메서드는 HTTP 메서드 중 하나로부터 파생되어 express 클래스의 인스턴스에 연결된다. 일반적으로 많이 사용하는 get, post, put, patch, delete, options 말고도 다양한 HTTP 메서드를 지원한다. 특수한 라우팅 메서드로는 all이 있는데, 이 메서드는 모든 요청 메서드에 연결되며 미들웨어 함수를 사용하는데 쓰인다.

// GET 메서드
app.get('/', function (req, res) {
  res.send('GET request to the homepage');
});

// POST 메서드
app.post('/', function (req, res) {
  res.send('POST request to the homepage');
});

// 모든 요청 메서드에 연결되는 all
app.all('/secret', function (req, res, next) {
  console.log('Accessing the secret section ...');
  next(); // pass control to the next handler
});

라우트 경로 PATH

경로는 요청 메서드와의 조합을 통해 요청이 이루어질 수 있는 엔드포인트를 정의한다. 문자열을 기본적으로 사용하고, 정규식 표현 역시 사용할 수 있다.

// / 경로
app.get('/', function (req, res) {
  res.send('root');
});

// /about 경로
app.get('/about', function (req, res) {
  res.send('about');
});

// 정규식을 사용해 특정 조건을 만족하는 경로
app.get(/.*fly$/, function(req, res) {
  res.send('/.*fly$/');
});

라우트 핸들러 HANDLER

핸들러는 들어온 요청에 따라 실행될 콜백 함수를 작성하여 요청을 처리한다. 하나의 함수는 물론 두개 이상의 함수, 함수가 요소인 배열도 사용이 가능하다. 이 때, 여러 개의 함수를 쓴다면 다음 함수로 넘길 때 반드시 next 객체를 지정해야 한다.

// 콜백 함수
app.get('/example/a', function (req, res) {
  res.send('Hello from A!');
});

// 2개의 콜백 함수 (next 객체 지정)
app.get('/example/b', function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});

// 함수가 담긴 배열 (next 객체 지정)
let cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}

let cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}

let cb2 = function (req, res) {
  res.send('Hello from C!');
}

app.get('/example/c', [cb0, cb1, cb2]);

응답 메서드 res.

응답(Response) 객체에 대한 메소드에도 여러가지가 있다. 만약 응답과 관련된 메서드가 사용되지 않으면 클라이언트 요청은 정지 된 채로 방치된다.

  • res.send() : HTTP 응답을 보낸다. 응답할 내용에는 Buffer object, string, object, boolean, array등 다양한 데이터를 담을 수 있다. 데이터 타입에 따라 응답 헤더 중 Content-Type이 자동으로 결정된다.
    • 내용이 string일 경우 "Content-Type" : "text/html"
    • 내용이 object, array일 경우 "Content-Type" : "application/json"
  • res.json() : JSON.stringify()를 사용하여 JSON 문자열로 변환된 응답을 보낸다.
  • res.end() : 응답 프로세스를 종료한다. body에 담길 내용이 없을 때(오류 등) 주로 사용된다.

미들웨어

미들웨어는 요청과 응답 사이 중간에서 요청에 필요한 기능을 더하거나 문제가 발생했을 때 처리를 중단하는 등 여러 추가적인 기능을 수행하는 역할을 한다. Express의 최대 강점 중 하나가 바로 이 미들웨어이다.

미들웨어 함수

미들웨어 함수는 Request Object, Response Object, 어플리케이션의 요청-응답 주기 중 다음의 미들웨어 함수에 대한 접근 권한을 가지는 함수이다. 쉽게 말해 요청 및 응답 객체에 대한 변경을 실행하는 함수이자, 추가적인 작업을 실행하는 다음 미들웨어 함수에 제어를 넘기는 함수이기도 하다.

요청-응답을 미들웨어 함수로 제어하는 이유는 프로세스를 함수화 하여 재사용성을 높일 수 있고, 단계별로 처리해 안정성을 높일 수 있기 때문이다. 서버에 들어오는 요청이 많고, 그에 따라 라우팅을 했을 때 동일하게 처리해야 하는 프로세스가 있다면 이를 함수화 하여 필요할 때 다시 호출하는 식으로 작성할 수 있기 때문에 편리하게 코드를 짤 수 있다. 또한 요청/응답 처리 중 특정 함수에서 문제 발생 시 처리를 중단하고 그에 따른 오류를 명시적으로 볼 수 있다.

보통 다음에 실행 될 미들웨어 함수를 next라는 변수로 표시하고, 다음 미들웨어 함수로 제어를 넘길 때 next() 와 같은 형식으로 다음에 실행할 함수를 호출하게 된다. 만약 next()를 사용하지 않고, 해당 함수에서 요청이 끝나지 않으면 요청은 정지된 채로 방치된다.

let express = require('express');
let app = express();

// 요청 객체에 현재 시간 속성을 추가하는 미들웨어 함수
let requestTime = function (req, res, next) {
  req.requestTime = Date.now();
  next(); // 속성 추가 후 다음 미들웨어 함수로 제어 넘김
};

app.use(requestTime); // requestTime 호출

app.get('/', function (req, res) {
  var responseText = 'Hello World!';
	// requestTime에서 추가된 속성 사용
  responseText += 'Requested at: ' + req.requestTime + ''; 
  res.send(responseText);
});

app.listen(3000);

미들웨어 사용

Express에서는 함수 말고도 사용할 수 있는 미들웨어가 많다. Express 내에서 작성하여 사용되는 미들웨어 외에도 기본적으로 제공 되는 미들웨어나 프레임워크 외부에서 미들웨어를 가져와서 사용 할 수도 있다. 사용 가능한 미들웨어는 다음과 같다.

  • 어플리케이션 레벨 미들웨어 : app.use()app.METHOD()를 이용해 앱 오브젝트에 바인드 되는 미들웨어. 앱 자체에 바인드되기 때문에 특정 조건이 아닌 모든 요청에서 실행된다.
  • 라우트 레벨 미들웨어 : 어플리케이션 레벨 미들웨어와 동일하나 express.Router()를 사용해 생성된 인스턴스에 바인드 되는 미들웨어.
  • 오류 처리 미들웨어 : 다른 미들웨어 함수와 동일한 방법으로 정의가 가능하나, 오류 처리 함수는 4개의 인수 (err, req, res, next)를 갖는다는 점에서 차이가 있다.
  • 기본 제공 미들웨어 : Express에서 기본적으로 제공하는 미들웨어 함수. 이전에는 body-parser나 cookie-parser 등 다양한 미들웨어가 기본적으로 제공 되었지만, 4.x 버전 이후부터는 별도의 모듈로 분리되었고 현재는 express.static만 제공하고 있다.
  • 써드파티 미들웨어 : 앞서 말한 body-parser나 cookie-parser, cors 등 별도의 모듈을 설치하여 사용하는 미들웨어. npm을 통해 따로 설치해서 require로 불러와서 사용할 수 있다.

서버 작성에 필요한 API

express.json()

Express에 내장된 미들웨어이며 JSON 형태로 들어오는 페이로드를 파싱한다. body-parser를 기반으로 동작한다. Express 4.16 이전 버전의 경우 body-parser라는 외부 모듈을 사용해야 한다. 파싱 이후에는 req.body로 페이로드에 접근할 수 있다.

const express = require("express");
const app = express();

app.use(express.json());

app.post('/', function(req, res){
  console.log(req.body);      // 사용자의 JSON 요청
  res.send(req.body);    // JSON 응답
});

app.listen(3000);
  • 매개변수에 {strict: false}을 입력하게 되면 객체나 배열이 아니더라도 파싱한다.

req.query

요청 객체의 속성으로 쿼리로 들어온 매개변수를 속성과 값으로 포함하고 있다. 파라미터를 활용해 서버가 가지고 있는 데이터 중 특정 데이터를 필터하여 응답하는 등으로 활용할 수 있다.

// GET /search?q=헤이트풀8
console.dir(req.query.q) // '헤이트풀8'

// GET /watch?v=ntDSgBRsSHK
console.dir(req.query.v) // 'ntDSgBRsSHK'

req.params

요청 객체의 속성으로 쿼리가 아닌 주소 자체에 포함 된 변수를 가져온다. 서버단에서

// GET /user/seil
console.dir(req.params.name) // 'seil'

cors

써드파티 미들웨어로 CORS 헤더를 매번 넣어줄 필요 없이 간단하게 처리를 할 수 있다. 모든 요청에도 사용할 수 있고, 특정 라우트에서만 허용할 수 있도록 넣어줄 수도 있다.

const cors = require('cors')

// 생략

app.use(cors()); // 모든 요청에 대해 CORS 허용

// 특정 라우트에서 허용
app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})

Uploaded by N2T