[TIL] Server Side Programming with Node.js

Ha Young Do·2021년 5월 27일
0

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
runtime: provides an environment for programs to run

V8 compiles JavaScript directly to native machine code
-> 자바스크립트를 컴파일 해주는 엔진으로 빌드된 자바스크립트가 구동중인 환경

event 기반의 non-blocking model이기 때문에 처리 속도가 빠르다.

Node.js Core Modules

따로 npm install 등의 설치 명령 필요없이 require('') 문법으로 사용 가능
e.g. filesystem, http, url, path ...

Node Package Manager (npm)

npm을 통해 설치한 dependency, devdependency (프로그램 구동에 필요한 package들) 들은 package.json에서 확인 가능하다

  • project information: name, version, description 등
  • script code: npm run (keyword) 로 간단하게 코드 실행 가능
    e.g. npm run start, npm run test ...
  • dependencies, devdependencies: 코드에 필요한 모듈을 dependency에 등록해주기 위해서는 --save로 다운받아야 한다

nodemon: 수정 사항이 있을 때 수동으로 서버를 키고 끌 필요 없이 저장시에 자동으로 수정 반영하여 코드 재실행 (npm install nodemon 으로 설치해 준 후 node 대신 nodemon 키워드 사용)

Node.js http module

Create Server

const http = require('http');

const server = http.createServer((request, response) => {
  // insert request handler 
});

server.listen(port, ip);

createServer method로 서버를 처음으로 생성한다. 이 때 인자로 넣어 주는 함수가 request handler로, 서버에 http 요청을 보낼 때마다 이 함수가 실행된다. 요청 처리를 위해서는 서버 생성 후 listen method를 호출해 서버를 켜 주어야 한다.

Routing

const requestHandler = function (req, res) {
  if (req.method === 'OPTIONS') {
  	// handle OPTIONS request
  } else if (req.url === '/messages') {
    // when url ends with '/messages'
  	if (req.method === 'GET') {
      // handle GET request
    } else if (req.method === 'POST') {
      // handle POST request
    }
  } else {
    // handle bad request
  }
}

routing이란, request method와 request url 등의 조건에 따라 알맞은 응답을 보낼 수 있도록 경로를 분기해 주는 과정이다. request handler 함수에서 인자로 request를 받아 와, req.method, req.url 등을 이용해 API에 적절하도록 라우팅을 해 준다.

Write Headers

if (req.method === 'OPTIONS') {
  res.writeHead(200, defaultCorsHeaders);
}

writeHead method를 이용해서 응답에 적절한 상태 코드와 헤더를 달아 준다.

CORS

const defaultCorsHeaders = {
  "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
};

Cross Origin Resource Sharing(CORS) 란, 한 source에서 실행 중인 앱이 다른 source의 resource에 접근할 수 있도록 브라우저에게 알려 주는 메커니즘을 말한다. 응답 헤더에 추가적인 정보를 달아 설정해 줄 수 있다. 위의 CORS 헤더는 차례로 다음과 같은 의미를 가진다:

  • 모든 도메인을 허용
  • GET, POST, PUT, DELETE, OPTIONS의 request method만 허용
  • 헤더에는 content-type, accept만 허용
  • preflight request는 10초동안 cache 된다 (10초 후에 다시 preflight request 보냄)

Parse & Write Body

if (req.method === 'POST') {
  let body = [];
  req.on('data', (chunk) => { // data 이벤트가 발생하면
    body.push(chunk);
  }).on('end', () => { // end 이벤트가 발생하면
    body = Buffer.concat(body).toString();
  });
}

request의 body는 Buffer 객체의 형태로 오기 때문에, 배열에 수집한 다음 문자열로 변환하는 방법을 사용한다. 각각 data 이벤트, end 이벤트에 event listener를 붙이는 방식으로 처리한다.

res.write(body);
res.end();

OR

res.end(body);

response의 body를 쓸 때에는 write method를 쓴 다음 end method로 보내거나, end method만 사용해서 한 번에 보내 줄 수 있다.

Data Storage

POST 요청을 처리해 주는 경우 request body에 담겨져 온 payload를 저장할 곳이 필요하다. JavaScript object에 key-value pair로 저장하거나, Node.js의 fs module을 사용하여 파일에 저장할 수 있다. (이외 database에 저장하는 방법은 이후에 학습한다.)

  1. JavaScript object
let messages = {
  // stored messages
};

// e.g. GET request

res.writeHead(200, defaultCorsHeaders);
const data = JSON.stringify(messages);
res.end(data);

가장 간단하지만 서버가 재시작 될 경우 저장된 내용이 보존이 되지 않는다.

  1. fs module
// e.g. GET request

fs.readFile('server/data.json', 'utf8', (err, data) => {
  if (err) throw err;
  res.writeHead(200, defaultCorsHeaders);
  res.end(data);
});

// e.g. POST request

let data = fs.readFile('server/data.json', 'utf8', (err, data) => {
  if (err) throw err;
  return data;
});

fs.writeFile('server/data.json', '{"results": [' + JSON.stringify(req.body) + ',' + data.slice(13), (err) => { // 파일에 json 형태로 저장된 object에 새로운 데이터를 추가해서 overwrite 한다
  if (err) throw err;
  res.writeHead(201, defaultCorsHeaders);
  res.end(body);
})

Express.js

Create Server and Basic Routing

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

let messages = {
  // stored messages
};

app.get('/messages', (req, res) => {
  res.status(200).send(messages);
});

const server = app.listen(3000, () => {
  console.log('server listen on port 3000');
});

express는 가장 간단한 Routing을 위해 app.${request method}(${경로}, (${middleware}), ${request handler}) 형태의 method를 사용한다. 두 번째 인자인 middlewear는 생략 가능하다. status method로 상태 코드를 붙여 줄 수 있고, send method 안에 인자로 body의 내용을 전달해 준다. listen method를 호출해 서버를 켜 줄 수 있다.

express.Router()

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

router.get('/', (req, res) =>{
  // handle GET request
})

router.post('/', (req, res) =>{
  // handle POST request
})

module.exports = router;

express에 내장된 router 기능을 이용하여 간편하게 routing이 가능하다.

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

const messagesRouter = require('위의 코드');
app.use('/messages', messagesRouter);

router 코드가 저장된 파일을 서버를 실행시키는 파일에 import 해 와서 app.use()의 두 번째 인자로 사용하면 간단하게 routing이 가능하다.

Middleware

express 서버에 요청이 전달되는 lifecycle 동안 거쳐 가는 함수이다. express로 작성된 앱은 일련의 middleware 함수 호출과 다름없다.

흔히 사용되는 middleware로는 request의 body를 간편하게 parsing해 주는 body-parser나 모든 요청에 CORS 헤더를 붙여주는 cors 등이 있다. request-response lifecycle의 끝에 오는 middleware가 아닌 경우 (명시적으로 response를 넘겨 주는 middleware가 아닌 경우) next()를 호출해 주어 다음 middleware로 순서를 넘겨 주지 않으면 요청 처리가 멈추게 된다.

Server Debugging

브라우저의 network tab에서 request 및 response를 볼 수 있다.

하이퍼링크 클릭으로 새로운 페이지를 rendering 시 수많은 GET request들이 이행되는 것을 볼 수 있다.

request 중 하나를 클릭하면 이렇게 헤더, response 등에 대한 정보를 볼 수 있다.

https://expressjs.com

profile
Codestates Software Engineering Full IM 28th

0개의 댓글