Node.js 웹 서버 구축 2가지

FeelSoo·2022년 4월 24일
0

CodeStates

목록 보기
21/43

Node.js를 통한 웹 서버 구축을 위한 2가지 방법은 다음과 같다.

1. HTTP 모듈을 사용하여 구축하는 방법

2. Node.js Express를 이용하여 구축하는 방법



공통점은 둘 다 Node.js 환경에서 이뤄지는 작업이지만 차이점은 후자의 코드가 더 직관적이고 단순하다.

또한 HTTP 모듈은 Node.js에 내장되어 있는 메소드이지만 Express 라이브러리는 Node.js의 내장 기능이 아니라 확장 기능이다. Express에는 자체 라우터를 제공한다.


< express 라우터 예시 >

const router = express.Router()

router.get('/lower', (req, res) =>{
  res.send(data)
})

router.post('/lower', (req, res) =>{
  // do something
})



** 참고 : MERN stack은 JavaScript 생태계에서 인기 있는 프레임워크인 MongoDB, Express, React, Node를 지칭하는 말


** 참고 : 웹서버란 무엇일까 ? HTTP 요청을 처리하고 응답을 보내 주는 프로그램을 말한다

링크텍스트 --- 참고 블로그



Node.js의 내장된 HTTP 모듈을 활용한 웹 서버 구축 예시


링크텍스트 --- HTTP 트랜잭션 해부


다음의 코드는 대문자를 소문자로, 소문자를 대문자로 변경시키는 기능을 구현한 웹 서버다.





const http = require('http');

const PORT = 4999;

const ip = 'localhost';

const server = http.createServer((request, response) => {  
  console.log(
    `http request method is ${request.method}, url is ${request.url}`
  );

  if (request.method === 'OPTIONS') {      
  response.writeHead(200, defaultCorsHeader); // response.writeHead(statusCode[, statusMessage][, headers]). 200,201, 404 등은 MDN status code 참고.
  response.end();   
} // 클라이언트 preflight request에 응답 반환. preflight은 메소드로 OPTIONS 사용


if (request.method === 'POST') {    // request 요청이 들어오면 ( POST 메소드이면 )
  let body =[]                      // 빈 배열 body 선언
  request.on('data', (chunk) => { body.push(chunk)})    // .on 메서드는 이벤트와 이벤트 핸들러를 결합시킨다. request에서 ‘data’라는 이름의 event가 발생한 경우,(chunk)=>{} 라는 이벤트 핸들러를 실행한다.
         .on('end', () => {body = Buffer.concat(body).toString(); response.writeHead(201, defaultCorsHeader); // 이어서 end 이벤트가 발생하면 Buffer에 바디 데이터를 문자열로 저장한다. 그리고 writeHead로 응답한다. (표준 폼인지 아닌지를)
          if(request.url === '/lower') { response.end(body.toLowerCase())}  // 요청.url이 소문자면 바디를 소문자로 바꾸고 페이지 실행을 종료
    else if(request.url === '/upper'){ response.end(body.toUpperCase())}    // 요청.url이 대문자면 바디를 대문자로 바꾸고 페이지 실행을 종료
      else{ response.writeHead(404, defaultCorsHeader); response.end();}    // 그 외의 경우 404 status 코드를 내보내고 페이지 실행을 종료
    })
  }
}); 

server.listen(PORT, ip, () => {   // listner 생성. (특정 인자 들어오면 콜백 함수 실행)
  console.log(`http server listen on ${ip}:${PORT}`);
});

const defaultCorsHeader = {     // response로 내보낼 헤더 정보
  '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
};

위와 같이 http 모듈을 사용해서도 충분히 웹 서버를 만들 수 있다. 하지만 복잡한 기능들을 가진 서버를 구현할 때는 코드들이 많아지면서 복잡해지기 때문에 최소한의 코드로 기능을 구현할 필요가 생긴다. 이는 직관적으로 이해가 가능하게끔 가독성을 높이기 위해서다. Express를 사용한다면 코드를 더 단순화 시킬 수 있다. 다음의 예시들을 살펴보자




1. POST 요청 등에 포함된 body(payload)를 구조화할 때

let body = [];						
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // body 변수에는 문자열 형태로 payload가 담겨져 있습니다.
}); 
// -------- HTTP 모듈 방식

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

const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()

// 생략
app.post('/api/users', jsonParser, function (req, res) {
  // req.body에는 JSON의 형태로 payload가 담겨져 있습니다.
}) 
// ------ Express 방식

요청이 들어왔을 때 응답 처리하기 위해 데이터를 재조립하는 코드다. HTTP 모듈로 작성했을 때는 .on 메서드를 활용하였다면 Express에서는 app.post를 활용하여 코드를 보다 더 단순화시켰다.




2. 모든 요청/응답에 CORS 헤더를 붙일 때

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()
} ------- http 모듈 방식

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


const cors = require('cors')
const app = express()
// 생략
app.use(cors()) // 모든 요청에 대해 CORS 허용

or

const cors = require('cors')

// 생략
// 특정 요청에 대해 CORS 허용
app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})

HTTP 모듈 방식에서는 Cors의 허용 범위에 대해서 모두 명시하였다면 Express를 통해서는 app.use나 app.get을 통해 코드를 더 줄일 수 있다.




3. 모든 요청에 대해 url이나 메소드를 확인할 때

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

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

app.use(myLogger);

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

app.listen(3000);

위 예시는 클라이언트의 요청이 들어올 때마다 'LOGGED'를 출력하는 예시다. Mylogger 변수를 살펴보면 next()를 볼 수 있고 이 함수가 있기에 app.use에 로드된 후 app.get을 불러올 수 있다. 없다면 app.use에서 함수는 종료된다.

app.get은 클라이언트의 요청이 GET이고 요청 경로 (endpoint ) 가 / 일 때 적용되는 함수다. 'Hello World!'를 출력한다

즉, 특정 enpoint가 아니라 모든 요청에 동일한 log를 적용하려면 출력하려면 app.use를 사용하면 된다.




4. 요청 헤더에 사용자 인증 정보가 담겨있는지 확인할 때(advanced challenges)

app.use((req, res, next) => {
  // 토큰 있니? 없으면 받아줄 수 없어!
  if(req.headers.token){
    req.isLoggedIn = true;
    next()
  } else {
    res.status(400).send('invalid user')
  }
})

다음은 HTTP 요청에서 토큰이 있는지 여부를 판단하여, 이미 로그인한 사용자일 경우 성공, 아닐 경우 에러를 보내는 미들웨어 예제이다.

로그인 없이 웹사이트에 접근을 시도했을 때, 로그인 창 등으로 되돌려 보내는 경우를 경험해 본 적이 있을 것이다.

서버에서는 요청에 포함된 데이터를 통해 미들웨어가 요구하는 조건에 맞지 않으면, 불량품으로 판단하고 돌려보내도록 구현할 수 있다.


위와 같이 express를 사용하면 http 모듈을 사용했을 때보다 코드를 더 줄일 수 있다. HTTP 모듈 방식이 server라는 하나의 변수에 응답과 요청에 대한 모든 프로세스를 담아논다면 express는 각 기능별 함수를 선언하고 하나의 프로세스가 처리되면 다음의 함수를 호출하여 응답을 처리한다.

express는 조금 더 객체지향적이라고 할 수 있다.

이와 같이 함수들의 호출 프로세스로 응답을 처리하는 '미들웨어' 방식이 Express의 큰 특징이다.

express로 서버를 구축하고 응답을 처리하기 위해 구현한 함수들을 미들웨어 함수라고 할 수 있다.

ex --- app.use(myLogger), app.get('/', function (req, res){} ... etc


위에 나열한 대로 4가지 경우에 미들웨어 함수를 사용하면 편리하다.

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

https://expressjs.com/ko/guide/writing-middleware.html --- 미들웨어 참고자료




미들웨어 함수들은 요청을 받아서 응답을 내보내기까지 함수들을 순차적으로 호출하여 진행하는데 각 처리가 정상적이라면 next()를 통해 다음 미들웨어로 넘어가지만 반대라면 즉시 오류를 응답으로 내보내게 된다.

profile
세상은 넓고 배울건 많다

0개의 댓글