Node.js 진영에서 가장 많이 사용되는 웹 프레임워크는 단연코 express다.
State of JS 2022의 설문 결과를 봐도 express가 압도적이고, 실제 다운로드 수도 가장 높다.
물론 가장 많이 사용된다는 것이 가장 좋다는 뜻도 아니고, 최근에는 express의 단점을 보완한 Nestjs가 많은 Github star를 받으며 치고 올라오고 있지만, 결국 Nestjs도 express 위에서 돌아가는 프레임워크이기 때문에 express에 대해 숙지하는 것이 좋고, 처음 백엔드에 대해 학습을 시작하는 사용자라면 Nestjs보다 상대적으로 난이도가 낮고 좀 더 직관적이기 때문에 이해도 쉬울 것이다.
따지고 보면 express도 결국 Node.js의 http 모듈 기반으로 만들어졌기 때문에 Node.js의 기본 모듈인 http까지 학습하는 것이 탄탄한 이해를 위해서 가장 좋다고 생각하기는 하지만, express만 알더라도 기본적인 서버를 만들고 이해하는 데는 부족함이 없을 것이다.
express를 사용하기 전에 Node.js의 http 모듈로 서버를 만들어 봤다면, express를 배우면서 얼마나 편하고 자동화 된 부분이 많은지 체감하게 되면서 즐거움과 동시에 고마움을 느낄 것이다.
http 모듈을 사용해 보지 않았다면, express는 직접 구현해야 하는 많은 부분을 사용자의 편의를 위해 간략화 되었다고 생각하고 앞으로 작성할 코드를 따라가보자.
express는 http와 달리 Node.js에 기본 탑재된 내장 모듈이 아니기 때문에, 직접 설치할 필요가 있다.
작업할 디렉토리를 하나 만들고 터미널에서 해당 path로 이동한 다음, npm init -y
명령어를 통해 Node.js 모듈을 초기화 해준다.
npm i express
로 해당 디렉토리에 express를 설치해준다.
이제 express
모듈을 불러와서 사용할 수 있다.
// import express external module
const express = require('express');
// create server('app')
const app = express();
// accept request to 'GET /'
app.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.status(200).send('<h1>Hello, World!</h1>');
});
app.listen(3000, () => {
// executed when server is started
console.log('server is running at 3000');
});
외부 모듈이기 때문에 require('express');
로 불러와서 사용하면 된다.
express()
로 서버 인스턴스를 생성한다.
Node.js의 createServer()
와 비슷하다고 생각하면 된다.
그 다음부터가 express의 특별한 부분인데, 서버의 URI를 if
문으로 직접 분기 처리할 필요 없이, app.get()
의 첫번째 인자로 URI를 전달하면 Request가 들어올 때 알아서 처리해준다.
URI 뿐만 아니라 http Method도 알아서 처리해 주는데 app.get()
, app.post()
, app.delete()
와 같이 서버 인스턴스의 메소드가 http Method와 대응되기 때문에 http Method를 알고 있다면, 직관적으로 이해하고 사용할 수 있을 것이다.
몇가지 사례를 살펴 보면,
app.get('/user')
라면 GET /user
요청을 처리할 것이고, app.post('/todo')
라면 POST /todo
에 대한 요청을 처리하는 식이다.
res.setHeader('Content-Type', 'text/html');
res.status(200).send('<h1>Hello, World!</h1>');
그리고 다음이 Response Header의 Content-Type과 Status Code를 설정하고 html을 클라이언트에 전송하는 부분이다.
app.listen(3000, () => {
console.log('server is running at 3000');
});
이후 app
인스턴스를 3000번 포트에서 listen하면 서버가 실행되면서 콜백 함수가 호출되고, Request가 들어오기를 계속해서 기다리는 서버가 실행된다.
전체적으로 express를 사용해 작성된 코드를 살펴보았으니, http 모듈로 작성한 코드와 비교 분석해보자.
// http
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, {
'Content-Type': 'text/html',
});
res.end('<h1>Hello, World!</h1>');
}
});
server.listen(3000, () => {
console.log('server is running at 3000');
});
// express
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.status(200).send('<h1>Hello, World!</h1>');
});
app.listen(3000, () => {
console.log('server is running at 3000');
});
매우 간단한 서버이기 때문에 코드의 길이나 가독성이 크게 차이나지는 않는다.
하지만 더 많은 URI가 추가된다면, express의 가독성이 훨씬 좋을 것이다.
// express
app.get('/', (req, res) => { ... });
app.post('/', (req, res) => { ... });
app.delete('/', (req, res) => { ... });
app.get('/a', (req, res) => { ... });
app.post('/a', (req, res) => { ... });
app.delete('/a', (req, res) => { ... });
app.get('/b', (req, res) => { ... });
app.post('/b', (req, res) => { ... });
app.delete('/b', (req, res) => { ... });
http.createServer((req, res) => {
// http
if (req.url === '/') {
if (req.method === 'GET') { ... }
if (req.method === 'POST') { ... }
if (req.method === 'DELETE') { ... }
}
if (req.url === '/a') {
if (req.method === 'GET') { ... }
if (req.method === 'POST') { ... }
if (req.method === 'DELETE') { ... }
}
if (req.url === '/b') {
if (req.method === 'GET') { ... }
if (req.method === 'POST') { ... }
if (req.method === 'DELETE') { ... }
}
);
express로 작성한 코드는 마치 영어 문장을 읽듯, get + URI 형태로 이루어져 있고, 코드도 더욱 간결하며, 각 URI에 하나의 메소드가 대응된다.
http로 작성한 코드는 하나의 콜백함수 안에 모든 분기 처리가 들어가 있어 URI가 늘어날수록 함수가 비대해지고, 들여쓰기가 깊어지는 것이 마치 콜백 지옥을 연상케한다.
코드가 길어지고 들여쓰기가 깊어지면 가독성이 떨어져서 사람이 읽기 힘들어지기 때문에 코드를 나눠야 되는데, express를 사용하면 이 작업을 보다 쉽게 할 수 있다.