'23.11.23(목) 웹 풀 사이클 데브코스 TIL
Node.js로 백엔드 서버 구축하기
백엔드(Backend)는 지난 TIL에서 소개한 것처럼 프론트엔드(Frontend)의 반대 개념으로
사용자에게 보이지 않는 서버측(Server-side) 개발 분야를 의미한다.
백엔드는 크게 웹서버, 웹 어플리케이션 서버, 데이터 베이스로 구성되고 Client 컴퓨터와 함께 통신하는 관계로 이루어진다.
웹 서버(Web Server)
웹 서버는 화면 내 내용이나 데이터의 변동이 없는 정적 페이지에 대응하는 역할을 한다.
웹 어플리케이션 서버(WAS)
웹 어플리케이션 서버는 데이터 처리/연산을 통해 화면의 내용이나 데이터가 변하는 동적 페이지를 처리한다.
데이터베이스(DB)
데이터베이스는 웹에서 사용될 데이터의 저장공간으로, 내일 학습에서 자세하게 다룰 예정이다.
Web Server와 WAS를 하나로 합쳐서 정적인 페이지를 보여줌과 동시에 동적인 처리를 수행하면 안되는가?
-> 가능하다.
그럼 왜 개념적으로 이를 분리할까?
기능 분리를 통해 서버의 부하를 줄인다.
-> 다양한 요청을 처리하기 위해 WAS에는 이미 부하가 큰 상태이기 때문에, Web Server를 분리해 빠르게 정적인 콘텐츠를 제공할 수 있도록 한다.
물리적으로 분리하여 보안을 강화한다.
여러 대의 WAS를 연결해 로드 밸런싱을 이용할 수 있다.
-> 오류가 발생하거나 큰 트래픽으로 인한 중단을 방지할 수 있다.
여러 언어의 웹 어플리케이션 서비스가 가능하다.
-> 하나의 서버에서 다양한 웹 어플리케이션의 활용이 가능해진다.
Node.js는 자바스크립트를 스크립트 언어를 넘어 프로그래밍이 가능하도록 지원하는 플랫폼이다.
Node.js로 Web Server와 WAS의 역할을 구현할 수 있고, 만든 WAS를 통해 DB와 통신하는 것도 가능하다.
즉, Node.js를 이용해 자바스크립트로 백엔드를 구현할 수 있다!
이제 Node.js 를 통해 백엔드의 기본적인 구조와 동작을 실습해 보자.
Node.js 에서 사용되는 함수와 개념을 직접 코드를 통해 학습해 보겠다.
//server.js
let http = require('http');
function onRequest(request, response){
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Hello Node.js')
response.end();
}
http.createServer(onRequest).listen(8888);
require()
- 모듈을 불러오는 함수
request
- 클라이언트가 서버에게 전달하려는 정보나 메시지를 담는 객체
response
- 서버에서 클라이언트로 응답 메시지를 전송 시켜주는 객체
writeHead()
- http response의 header를 설정하기 위한 http 모듈의 함수
response.write()
- 서버가 브라우저에 response 객체를 전송하는 함수
response.end()
- 응답을 종료하는 함수
http.createServer()
- 서버 인스턴스를 생성하는 함수
listen()
- 서버 생성 후 접속 포트를 할당하는 함수
위 코드를 해석하면
1. node의 http
모듈을 require()
함수를 통해 불러온다.
2. onRequest()
라는 함수를 만들어 http 요청에 응답한다.
-> 정상적으로 접속된 http 200 인 경우 text/html
타입의 컨텐츠를 보낸다.
-> 'Hello Node.js' 라는 컨텐츠(text/html
타입)를 담아 보낸다.
-> 응답을 종료한다
3. createServer().listen(8888)
을 통해 서버를 열어 8888번 포트와 연결하고, 응답을 위해 만든 onRequest
함수를 콜백함수로 전달한다.
위 코드를
터미널에 node server.js
를 통해 실행하면
성공적으로 접속해 응답으로 보낸 메시지를 확인할 수 있다.
다른 코드에서 서버의 동작을 제어할 수 있도록 server.js
를 모듈화 해보자.
//server.js
let http = require('http');
function start() {
function onRequest(request, response){
// request와 response의 값은 node가 알아서 넣어줌
// reponse를 가지고
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Hello Node.js')
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.openServer = start;
//index.js
let server = require('./server');
server.openServer();
exports.[객체 이름] = [모듈 이름]
- 객체에 모듈을 담아 내보내는 함수
server.js
에서 onRequest()
기능과 createServer()
기능을 합친start()
라는 함수를 openServer
이라는 이름에 담아 모듈로 내보낸다.
-> 실습을 진행하면서 관계를 명확히 이해할 수 있도록 함수 이름과 저장할 객체 이름을 다르게 설정하였다.
같은 디렉토리에 위치한 index.js
에서 방금 만든 server.js
파일을 import해 openServer
메서드를 실행할 수 있는 구조로 만들었다.
터미널에 node index.js
를 입력하면
아까와 같은 페이지 결과가 나오지만,
index.js
와 server.js
를 분리할 수 있게 되었다.
Route는 경로라는 뜻이고, Router는 경로를 잡아주는 길잡이라는 뜻이다.
웹에서 라우팅(Routing)은 요청에 따라 정해진 경로, 즉 요청된 페이지로 안내하는 방법이다.
일반적으로 웹 페이지를 띄워주는 동작 자체를 라우팅이라고 표현하기도 한다.
//server.js
let http = require('http');
let url = require('url')
function start(route) {
function onRequest(request, response){
let pathname = url.parse(request.url).pathname;
route(pathname)
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Hello Node.js')
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.openServer = start;
//router.js
function route(pathname) {
console.log('pathname : ' + pathname);
}
exports.sendPathname = route;
//index.js
let server = require('./server');
let router = require('./router');
server.openServer(router.sendPathname);
routing 하는 로직을 분리해 router.js
에 만들어보자
우선 지금은 보여줄 페이지가 하나 이므로 router에서 URL을 콘솔에 출력할 수 있도록 route()
함수를 만들어 테스트 해 보았다.
router.js
의 route()
함수를 sendPathname
이라는 객체에 담아 export 한다.index.js
에서 openServer
객체에 sendPathname
을 전달한다.server.js
에서 url.parse(request.url).pathname
를 통해 url을 받아올 수 있게 설정한다.node index.js
를 입력한 뒤 주소창 URL 경로에 routeTest를 입력했을 때
입력한 /routeTest 경로가 콘솔창에 잘 출력됨을 확인할 수 있다.
이제 실제 상용되는 웹 페이지처럼 경로에 따라 다른 페이지를 라우팅 해보자.
//requestHandler.js
//url을 통해 접속이 되면 handle을 통해 main, login 함수가 호출되므로 response를 각 함수가 받도록 함
function main(response) {
console.log('main');
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Main Page')
response.end();
}
function login(response) {
console.log('login');
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Login Page')
response.end();
}
let handle = {}; // key: value
handle['/'] = main;
handle['/login'] = login;
exports.handle = handle;
//server.js
let http = require('http');
let url = require('url')
function start(route, handle) {
function onRequest(request, response){
let pathname = url.parse(request.url).pathname;
route(pathname, handle, response)
}
http.createServer(onRequest).listen(8888);
}
exports.openServer = start;
//router.js
function route(pathname, handle, response) {
console.log('pathname : ' + pathname);
if (typeof handle[pathname] == 'function') {
handle[pathname](response);
} else {
response.writeHead(404, {'Content-Type' : 'text/html'});
response.write('<h1>404 Not found</h1>')
response.end();
}
}
exports.sendPathname = route;
//index.js
let server = require('./server');
let router = require('./router');
let requestHandler = require('./requestHandler')
server.openServer(router.sendPathname, requestHandler.handle);
main
과 login
이라는 페이지 두 가지를 만들어 보자.
server.js
의 onRequest()
함수 내에 있던 route 로직을requestHandler.js
라는 파일의 각 경로 별 라우팅 함수로 옮김reuquestHandler.js
의 handle
이라는 딕셔너리에 경로가 키로 입력되면 각 라우팅 함수가 반환되도록 설정router.js
에서 HTTP status가 404인 경우 Not found 메시지를 응답함.이제 비로소 요청에 따라 다른 웹 페이지를 띄워주는 백엔드의 기초적인 모습을 만들 수 있게 되었다.