Server) Node.js Express 활용하기

백준우·2021년 11월 3일
1

Server

목록 보기
2/4
post-thumbnail

저번시간 서버와 통신하는 로그인화면을 HTTP 트랜잭션을 통해 구성하였다.
이번엔 JavaScript 생태계에서 인기있는 프레임워크인 Express.js를 이용해 HTTP트랙잰션으로 구성한 로그인서버를 리팩토링 하기 위해 Express에 대해 전반적으로 정리를 해보겠다.
(Express.js또한 Node.js상에 동작하는 웹개발 프레임워크이며 가장 많이 쓰이는 편이다.)

1. Express의 강정, HTTP트랜잭션과의 차이점

1.Middleware를 추가하기 편하다.
이때 Express의 미들웨이란 서버에 요청이 들어올 경우 그에따른 응답을 보내주는데 응답을 보내주기전 미들웨이가 지정한 동작을 수행하고 보내주게 된다.

2.자체 라우터를 제공한다.
URL 요청에 따라 어플리케이션 응답을 한다.

TIP) 미들웨이의 정의
미들웨어는 양 쪽을 연결하여 데이터를 주고받을 수 있도록 중간에서 매개 역할을 하는 소프트웨어, 네트워크를 통해서 연결된 여러 개의 컴퓨터에 있는 많은 프로세스들에게 어떤 서비스를 사용할 수 있도록 연결해주는 소프트웨어라고 정의된다.

2. Express 시작하기

  1. Node.js를 설치를 한뒤 vsCode로 작업 디렉토리를 오픈한뒤 Terminal에서 "$ npm init"을 해줍니다.
  2. 그뒤 작업하는 디렉토리에서 "$npm install express --save"를 해줍니다.

이제 위 디렉토리에서 Express를 사용할 수 있게 되었다.

3. 서버 생성하기 & 기본라우터

이전 HTTP에서 서버를 생성하려면 "createServer"로 서버를 생성하여 ".on"이라는 메소드를 사용하여 수신된 데이터를 처리하였고 "writeHead(송신 데이터의 Header를 지정)"등 메소드를 통해 서버의 기능을 구현하였다.

이번에는 Express를 통해 구현을 할것이며 Express 공식홈페이지에 있는 Hello World예제를 통해 시작해보겠다.
URL(/)에대해 Hello world를 출력하고 그 외의 요청은 404오류를 출력하는 코드이다.

const express = require('express') //express실행하기 위해 불러와서 "express"라는 변수에 넣어줬다.
const app = express() //app에 Express 인스턴스를 만듭니다.
const port = 3000 // port번호는 

app.get('/', (req, res) => { //get메소드를 통해 GET요청에 대한 처리를한다. 
  res.send('Hello World!') //GET요청에 대해 'Hello World!'라는 문구를 보낸다.
})

app.listen(port, () => { //HTTP트랜잭션과 동일하게 listen을 통해 서버를 실행한다. 
  console.log(`Example app listening at http://localhost:${port}`) 
})


브라우저 화면에 문구가 정상적으로 출력되는것을 확인하였으며 개발자 도구를 통해서도 정상적으로 서버가 통신한것을 확인했다.

이처럼 HTTP보다는 어떤기능을 하는지에 대해 좀더 편하게 확인 할수 있다. 이러한 기능은 Express의 라우팅 기능덕분이라고 할수 있는데 '/'이라는 URL에서 실행하는 함수가 어떤 역할을 하는지 매우 쉽게 알 수 있다.

이러한 라우팅은 URL및 특정 HTTP 요청방법(GET,POST 등)인 특정 끝점에 대한 클라이언트 요청에 어플리케이션이 응답하는 방식을 결정하는것을 말합니다.
아래 라우터의 기본 공식의 집합을 통해 하나의 서버를 만들수 있다 .

기본 공식

app.METHOD(PATH,HANDLER) 
  • app의 인스턴스입니다 express.
  • METHOD소문자 로 된 HTTP 요청 메서드 입니다.
  • PATH 서버의 경로입니다.
  • HANDLER 경로가 일치할 때 실행되는 함수입니다.

    예시) URL('/upper')에서 POST요청일 경우 실행하는 함수
app.post('/upper', function(req,res){
  res.send('Get a POST request') 
})

4.Middleware에 대해

컨베이어 벨트 공정을 예시로 들어보겠다.
각 공정마다 부품을 추가하고 모든 공정이 완료 되면 하나의 자동차가 나오고 중간에 문제가 발생할경우 최종적으로 불량품이 나온다. 이때 Middleware는 중간에 불량공정을 걷어내는 역할을 한다.
즉, Middleware는 프로세스 중간에 관여하여 특정 역할을 수행한다. (console.log를 활용하여 데이터나 정보를 확인 하는방법이 있다.)

위 그림은 Middleware의 역할을 보여주는 가장 간단한 예시이다. "app.get" 인스턴스에서 function을 확인하면 별다른 이벤트가 일어나지 않는다. 따라서 next() 함수를 호출하여 다음 미들웨어로 데이터를 전달하고 있습니다.

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

TIP) Payload: 데이터 전송부분 중에서 실제로 사용자가 필요로 하는 부분

  1. 모든 요청에 대해 url이나 메소드를 확인할 때
    (모든 요청에 동일한 미들웨어를 적용하려면 소드 app.use 를 사용한다.)
const express = require('express');
const app = express();
...
const myLogger = function (req, res, next) {
  console.log('LOGGED'); // 이 부분을 req, res 객체를 이용해 고치면, 여러분들은 모든 요청에 대한 로그를 찍을 수 있습니다.
  next();
};
...
app.use(myLogger);
...
app.get('/', function (req, res) {
  res.send('Hello World!');
});
..
app.listen(3000);

모든 요청에 대해 myLogger을 통과하는것을 확인 할 수 있다.

  1. POST요청 등에 포함된 body(payload)를 구조화 할때
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()
...
app.post('/api/users', jsonParser, function (req, res) {
  // req.body에는 JSON의 형태로 payload가 담겨져 있습니다.
})

위 방법이 일반적이나 Middleware를 통하여 위 과정을 간단히 처리 할 수 있다.
express.json([Options])을 활용해서이다.

const express = require('express')
const app = express()
app.use(express.json())
...
app.post('/api/users', function (req, res) {
  // req.body에는 JSON의 형태로 payload가 담겨져 있습니다.
})
  1. 모든 or 특정 요청/응답에 CORS헤더를 붙일때
//모든 요청에 대해 CORS적용시 
const cors = require('cors')
...
app.use(cors()) // 모든 요청에 대해 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'})
})
  1. 요청 헤더에 사용자 인증 정보가 담겨있는지 확인 할때
    HTTP 요청에서 토큰이 있는지 여부를 판단하여, 이미 로그인한 사용자일 경우 성공, 아닐 경우 에러를 보내는 미들웨어 예제입니다.
app.use((req, res, next) => {
  // 토큰유무 확인
  if(req.headers.token){ //token이 있을경우
    req.isLoggedIn = true;
    next()
  } else { //없을 경우 error 메세지인 400과 'invalid user'을 송신한다.
    res.status(400).send('invalid user')
  }
})

5.express.js에서 POST 데이터 처리하기

  • 클라이언트에서 서버로 request body에 담아서 보내는 여러 데이터들을 express에서 바로 처리할 수는 없기에, body-parser라는 모듈을 사용해서 express에서 처리할 수 있도록 합니다.
  1. express.json([Options])
    이 메소드는 JSON만을 파싱하고 request내의 Content-type과 해당 메소드에 일치하는 request만을 처리하는 Middleware를 반환 합니다. Middleware로 처리되어 body객체내에는 request객체 내에서 파싱된 데이터들이 있어야하지만 파싱할 데이터가 없거나 Content-type이 일치하지 않거나 에러가 난다면 body는 빈객체가 반환될겁니다.
    위 메소드에 쓰이는 Option은 다음과 같다
이름설명기본값
typetype에 일치된 request만을 처리하는 Middleware를 반환한다."application/json"
reviverJson.parse()의 두번째 인자로 전달되며 두번째 인자는 결과를 반환하기전에 변형하는 함수가 들어감null
strict배열과 객체만 받아드릴지 결정한다. 비활성화 되면 Json.parse가 처리할 수 있는 모든것을 처리하게 됩니다.true

사용예시)

  • Front-End(Java Script)
fetch("/", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        user: {
            name: "baek",
            email: "baek@example.com",
        },
    }),
});
  • Back-End(express.js)
app.use(express.json());
app.post("/", function (request, response) {
    console.log(request.body.user.name);
    console.log(request.body.user.email);
});
  1. express.urlencoded([Option])
    이 메소드는 urlencoded된 body만을 파싱하고 request내의 Content-type과 해당 메소드에 일치하는 request만을 처리하는 Middleware를 반환 합니다.
    이 메소드에 쓰이는 옵션은 다음과 같다.
이름설명기본값
typetype에 일치된 request만을 처리하는 Middleware를 반환한다."application/x-www-form-urlencoded"
extendedURL-인코딩 된 데이터를 querystring 라이브러리(false)와 qs 라이브러리(true)중 선택하여 파싱true
parameterLimitURL인코딩된 데이터에서 처리할 수 있는 매개변수들의 상한을 설정한다. 상한선을 초과할시 에러 발생1000

사용예시)

  • Front-End(Java Script)
<form method="post" action="/">
    <input type="text" name="user[name]" />
    <input type="text" name="user[email]" />
    <input type="submit" value="Submit" />
</form>
  • Back-End(express.js)
app.use(express.urlencoded());
app.post("/", function (request, response) {
    console.log(request.body.user.name);
    console.log(request.body.user.email);
});

!) urlencoded란? &으로 분리되고, "=" 기호로 값과 키를 연결하는 key-value tuple로 인코딩되는 값입니다.
POST 요청은 보통 HTML 양식을 통해 서버에 전송하며, 서버에 변경사항을 만듭니다. 이 경우의 콘텐츠 유형(Content-Type)은 <"form"> 요소의 enctype 특성이나 <"input">, <"button"> 요소의 formenctype 특성 안에 적당한 문자열을 넣어 결정한다.
예시)
urlencoding을 통해 urlencoded방식으로 데이터를 전송한다.

   <FORM  NAME="폼객체의이름"
   METHOD="[GET|POST]"
   ACTION="URL"
   ENCTYPE="요청패킷의 데이터부분에 존재하는 데이터의 인코딩방식">
  1. express.text([Options])
    이 메소드는 body를 문자열로 보고 파싱하고, request내의 Content-type과 해당 메소드의 type옵션이 일치하는 request 만을 처리하는 미들웨어를 반환합니다.
    이 메소드에 쓰이는 옵션은 다음과 같다.
이름설명기본값
typetype에 일치된 request만을 처리하는 Middleware를 반환한다."text/plain"
defaultCharsetContent-type 헤더에서 기본 문자 셋이 지정되지 않았을 경우 문자 셋을 지정하는 역할을 한다."utf-8"


  1. express.raw([options])
    이 메소드는 body를 Buffer로 보고 파싱하고, request내의 Content-type과 해당 메소드의 type옵션이 일치하는 request 만을 처리하는 미들웨어를 반환합니다.
    이 메소드의 옵션은 하나밖에 없다.
이름설명기본값
typetype에 일치된 request만을 처리하는 Middleware를 반환한다."application/octet-stream"

- 느낀점

  1. HTTP트랜잭션보다 직접 헤더를 설정하는부분이과 body의 데이터를 파싱하는 부분이 간단하게 느껴진다.
  2. 많은 기업들이 express를 사용한다고 하니 더욱 깊은 공부가 필요할것으로 보인다.
  3. 이전에 제작한 서버를 통한 로그인창 구현을 express로 리팩토링 해봐야겠다.

- 목표

  • HTTP트랜잭션을 활용한 서버를 리팩토링 해볼것
  • 데이터를 단순히 거치는것이 아닌 데이터를 서버내에서 판단하여 로그인창으로 데이터를 송신할것
  • 아직 JSON데이터만 다뤄보았지만 다양한 데이터를 예시로 다뤄보아야 겠다.

참고자료

profile
이게 되네?

0개의 댓글