본문 바로가기

개념/Node.js

[Node.js] Express 시작하기, 미들웨어와 라우터 사용법

👯‍♀️👯 MERN stack

MERN stack은 javaScript 생태계에서 인기 있는 프레임워크인 MongoDB, Express, React, Node.js를 합해서 지칭하는 말이다.

이중에서 Express는 Node.js의 환경에서 웹 서버,또는 API서버를 제작하기 위해 사용되는 프레임 워크이다.

 

M. E. R. N!!


🏃🏻‍♀️ Express 시작하기

 

1. Express 설치 (참고 - 공식문서)

npm install express

 

2. Express 기본골격 (참고 - 공식문서)

const express = require('express') //📌 express 불러오기
const app = express() //📌 express()로 만든 서버객체를 app에 할당
const port = 3000 

app.get('/', (req, res) => { //📌 이런식으로 app.method 사용하여 request에 대한 response처리
  res.send('Hello World!')
})

app.listen(port, () => { //📌 어떤 포트에 연결하여 청취할지 결정
  console.log(`Example app listening on port ${port}`)
})

일단 여기까지 하면 이제 기본적으로 request와 response를 다룰 준비는 끝난 것이다.


💡 Express의 차별성

본격적으로 response처리를 하는 방법을 알기에 앞서 알아둘 것이 있다.

Express는 Node.js HTTP 모듈을 기반으로 하지만 조금 더 업그레이드된 기능들이 있다.

그 차별점은 아래와 같다.

  • 미들웨어를 추가할 수 있다.
  • 라우터를 제공한다.

이제 아래에서는 Express만의 차별화된 '라우터'와 '미들웨어'를 사용하여 response처리를 해볼 것이다.

 

미들웨어 + 라우터!!


🚦라우팅 처리하기

 

라우팅(Routing)이란 메서드와 url로 분기점을 만드는 것을 말한다.

라우팅은 클라이언트 가 보낸 HTTP 요청을 받아서 요청에 해당하는 Endpoint에 따라 서버가 응답하는 방법을 결정하는 일을 말한다.

 

 

🔹 01: 기본 라우팅하기

 

Express에서 기본적인 라우팅 방법은 아래와 같다.

💡 Express 기본 라우팅 방법: app.METHOD(PATH, HANDLER)

-app :  express의 인스턴스(const app=express()로 만든것)
-METHOD : HTTP 요청 메소드 (GET,POST,PUT,PATCH,OPTIONS,DELETE..)
-PATH : 서버에서의 경로 (문자열, 문자열 패턴 또는 정규식)
-HANDLER : 라우트가 일치할 때 실행되는 함수, (requset,response)=>{}, request와 response객체를 받는다.

아래의 예시를 참고

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

// GET method route
app.get('/', function (req, res) { //💡 '/'endpoint로 get 요청했을 때에 라우트
  res.send('GET request to the homepage');
});

// POST method route
app.post('/', function (req, res) {  //💡 '/'endpoint로 post 요청했을 때에 라우트
  res.send('POST request to the homepage');
});

app.listen(3000, () => {
  console.log("The server is listening on port 3000")
});

express()로 express객체를 만들어주고, 거기에 http 메서드를 사용하여 라우팅()을 시킨다.

 

http모듈을 사용하는 방식보다 아주아주 직관적이고 편리하다.

위의 코드를 Node.js의 http모듈을 사용해서 만드는 방법과 비교하고 싶다면 아래의 '더보기'클릭

------------

더보기
const http = require("http");

const server = http.createServer((req, res) => {
  if(req.url === '/') {
    if (req.method === 'GET') {
      res.end('GET request to the homepage')
    } else if (req.method === 'POST') {
      res.end('POST request to the homepage')
    }
  }
})

server.listen(3000,()=>{
    console.log("The server is listening on port 3000")
})

------------

 

 

🔹 02: app.route()로 한 경로 + 여러 method를 직관적으로 라우팅하기

 

💡 app.route() 사용방법: : app.route(PATH).METHOD(HANDLER)

-app :  express의 인스턴스(const app=express()로 만든것)
-METHOD : HTTP 요청 메소드 (GET,POST,PUT,PATCH,OPTIONS,DELETE..)
-PATH : 서버에서의 경로 (문자열, 문자열 패턴 또는 정규식)HANDLER : 라우트가 일치할 때 실행되는 함수,(requset,response)=>{}, request와 response객체를 받는다.

이 방법은 기본적으로 01번 방법으로 커버가 가능하나,

한 경로에 여러가지 메소드를 작성해야 할 때 더 이쁘게 작성할 수 있는 방법이다.

 

아래를 보면 .route()를 사용하면 중복을 피할 수 있어 더욱 직관적임을 확인할 수 있다.

app.route('/book')
  .get(function(req, res) {
    res.send('Get a random book');
  })
  .post(function(req, res) {
    res.send('Add a book');
  })
  .put(function(req, res) {
    res.send('Update the book');
  });
  
  
//--방법1로 적기(위의 코드와 같다)--
app.get('/book',function(req, res) {
    res.send('Get a random book');
})
app.post('/book',function(req, res) {
    res.send('Add a book');
})
app.post('/book',function(req, res) {
    res.send('Update the book');
})

 

 

🔹 03: express.Router사용하여 모듈식으로 라우팅하기

 

아무리 Express가 간결하고 직관적이라고 해도, 라우팅이 많아지면 줄줄이 연결되 읽기 힘들 수 밖에 없다.

 

이를 위해서 존재하는 것이 바로, express.Router!!

express.Router를 사용하면 한 경로안에서 서로 다른 엔드포인트메서드에서 일어나는 response를 정의하고

.use()메서드를 사용해 모듈식으로 가져다 붙일 수 있다.

경로별로 폴더별로 나누어서 요청을 처리할 수 있는 것이다!!

 

좀 말이 어렵지 예시로 보면 쉽다.

//💡🔗 app.js 라우터 파일 (여기가 기본 라우팅이 진행되고, 모듈 라우터를 가져다 붙인다)

const express = require('express');
const birds = require('./birds);
const app = express();

app.use('/birds',birds) //📌use를 써서 경로와 라우터를 연결한다
app.listen(3000, () => {
  console.log("The server is listening on port 3000")
});

 

//💡🔗 birds.js 라우터 파일 (여기서는 /birds에서 일어나는 일들만 정의한다.)

const express = require('express');
const router = express.Router(); //기본 라우팅과는 다르게 expressd의Router메서드로 객체를 만든다.

//birds 라우터에서만 사용할 미들웨어를 정해줄 수 있다.
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
//birds 라우터에서만 사용할 기본 라우트를 정해줄 수 있다.(/birds/)
router.get('/', function(req, res) {
  res.send('Birds home page');
});
//birds 라우터에서만 사용할 다른 라우트를 정해줄 수 있다.(/birds/about)
router.get('/about', function(req, res) {
  res.send('About birds');
});

module.exports = router;

🦾 미들웨어 사용하기

 

미들웨어(Middleware)란 공장의 컨베이어 벨트를 이용한 공정과 비슷하다.

컨베이어 벨트 위에 올라가 있는 요청(Request)에 필요한 기능을 더하거나,

문제가 발견된 불량품을 밖으로 걷어내는 역할을 할 수 있다.

또한 모든 요청에 하나하나 설정해주지 않아도 자동화시킬 수 있기 때문에 아주 편리하다.

 

request:짧은 강아지/middlewear:쭉 늘리는 사람/response:긴강아지

 

쉽게 접할 수 있는 미들웨어를 사용하는 상황은 다음과 같다.

  1. POST 요청 등에 포함된 body(payload)를 구조화할 때(쉽게 얻어내고자 할 때)
  2. 모든 요청/응답에 CORS 헤더를 붙여야 할 때
  3. 모든 요청에 대해 url이나 메서드를 확인할 때
  4. 요청 헤더에 사용자 인증 정보가 담겨있는지 확인할 때

이제 이 네가지 경우에 대해 살펴볼건데

이 외에 미들웨어 사용에 대해 좀더 자세히 알고 싶다면 공식문서를 참고한다.

 

 

🔹 01: 미들웨어로 POST요청 등에 포함된 body(payload) 구조화하기

 

Node.js로 HTTP body(payload)를 받을 때에는 Buffer를 조합해서 다소 복잡한 방식으로 body를 얻을 수 있다.

이때 아래와 같이 네트워크 상의 chunk를 합치고, buffer를 문자열로 변환하는 작업이 필요하다.(참고)

이 과정이 없으면 우리는 컴퓨터의 언어로 쓰여져있는 data를 읽고 가공해서 저장하거나 다시 response로 보내는 등의 행위를 못할 것이다.

//기본 Node.js의 http모듈을 이용하면 이런식이다.
let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // body 변수에는 문자열 형태로 payload가 담겨져 있습니다.
});

하지만 Express의 내장 미들웨어 express.json()을 사용하면 아래와 같이 간편하게 끝낼 수 있다.

const jsonParser = express.json();

// 생략
app.post('/api/users', jsonParser, function (req, res) {
 //📌 중간에 jsonParser로 chunk > Buffer > 문자열로 변환의 과정을 모두 실행했기 때문에
 //우리가 이 안에서 가공할때 있는 읽을 수 있는 형태에서 바로 가공 가능하다.
})

또한 오는 모든 payload들을 따로 명시하지 않아도 자동으로 express.json()과정을 거치고 싶다면

.use()를 사용하면 된다.

app.use(express.json({strict: false}));

app.post('/api/users', function (req, res) {
 //따로 jsonParser를 연결해주지 않아도 자동으로 string화 된다.
})

🛑 express.json({strict: false}) 설정을 해줘야 다양한 포맷(객체, 배열, 숫자, 문자열, 불리언,null)을 변환한다. 만약 따로 false설정을 하지 않으면 객체와 배열만 허용하여 변환한다.

 

 

 

 

🔹 02: 미들웨어로 응답/요청에 CORS헤더 붙이기

 

Node.js http 모듈을 이용한 코드에 CORS 헤더를 붙이려면, 응답 객체의 writeHead 메서드를 이용할 수 있다.

Node.js에서는 이 메서드 등을 이용하여 라우팅마다 헤더를 매번 넣어주어야 하고,

OPTIONS 메서드에 대한 라우팅도 따로 구현해야 해서 아주 복잡하기 짝이 없다.

Node.js http 버전 엿보고 싶다면 아래의 더보기 클릭

------------

더보기
const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

// 생략
if (req.method === 'OPTIONS') {
  res.writeHead(201, defaultCorsHeader);
  res.end()
}

------------

 

cors 미들웨어를 사용하면 이 과정을 간단하게 처리할 수 있다.

 

일단 터미널창에서  npm으로 써드파티모듈인 cors를 깔아주고

npm install cors

 

이렇게 .use()를 이용해서 모든 요청에 대해 cors를 허용해준다.

그럼 프리플라이트를 비롯한 모든 요청에 허용해준다.

const cors = require('cors');

// 생략
app.use(cors());

 

만약 특정요청에만 cors를 허용해줘야한다면 아래와 같이 할 수 있다.

const cors = require('cors')

// 생략
app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})

 

🔹 03: 직접만든 미들웨어로 모든 요청에 대해 url이나 메서드를 확인하기

 

미들웨어는 위에서처럼 이미 만들어진 cors()나 json()말고도

필요한대로 만들어서 붙이는 것도 가능하다.

 

많은 요청이 들어오는데, 특수하게 그 모든 요청들에 대해 어떤 절차를 걸쳐 무엇인가를 확인해줘야 할때

미들웨어를 만들어 사용하면 아주 편리하게 처리할 수 있다.

 

택배 컨베이어를 생각하면 일단 택배를 포장하는 공정(미들웨어)을 만들어 붙여놓고

그다음 서울 경기지역 나누어 처리(라우팅)하는 등의 사례를 생각할 수 있을 것이다.

 

무조건 박스를 포장하고 서울/경기 택배 분리등의 일 수행

 

공식문서 를 참조하여 미들웨어 함수를 만들때 어떤 요소들이 들어가야 하는가 알 수 있다.

여기서 중요한게 .next()인데 .next()를 사용하면 무조건 다음 미들웨어로 넘어가도록 할 수 있기 때문에

어떤 미들웨어든 제작해서 적용할 수 있는 것이다.

(출처: express 공식문서)

아래의 예시들이 이런 미들웨어 사용한 사례이다.

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

const myLogger = function (req, res, next) {
  console.log('LOGGED');
  next();
};

app.use(myLogger);

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000);
const express = require('express');
const app = express();

const myLogger = function (req, res, next) {
  //req를 활용합니다.
  next();
};

app.use(myLogger);

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000);

참고로 위 코드에서 로거(logger) 라는 네이밍으로 미들웨어를 만들어서 사용하고 있는데, 

로거는 디버깅이나, 서버 관리에 도움이 되기 위해 console.log로 적절한 데이터나 정보를 출력하는 코드를 말한다.

데이터가 여러 미들웨어를 거치는 동안 응답할 결과를 만들어야 한다면,

미들웨어 사이사이에 로거를 삽입하여 현재 데이터를 확인하거나, 디버깅에 사용할 수 있어 유용하다.

 

 

🔹 04: 요청 헤더에 사용자 인증 정보가 담겨있는지 확인하기

 

HTTP 요청에서 토큰이 있는지를 판단하여,

이미 로그인한 사용자일 경우 성공, 아닐 경우 에러를 보내는 미들웨어 예제이다.

app.use((req, res, next) => {
  // 토큰이 있는지 확인, 없으면 받아줄 수 없음.
  if(req.headers.token){
    req.isLoggedIn = true;
    next();
  } else {
    res.status(400).send('invalid user')
  }
})