Express.js 마스터하기: 깊게 들어가기
James Reed
Infrastructure Engineer · Leapcell

Express는 Node.js에서 매우 일반적으로 사용되는 웹 서버 애플리케이션 프레임워크입니다. 기본적으로 프레임워크는 특정 규칙을 준수하는 코드 구조이며 다음과 같은 두 가지 주요 특징이 있습니다.
- API를 캡슐화하여 개발자가 비즈니스 코드 작성에 더 집중할 수 있도록 합니다.
- 확립된 프로세스와 표준 사양을 가지고 있습니다.
Express 프레임워크의 핵심 기능은 다음과 같습니다.
- 다양한 HTTP 요청에 응답하도록 미들웨어를 구성할 수 있습니다.
- 다양한 유형의 HTTP 요청 작업을 실행하기 위한 경로 테이블을 정의합니다.
- 템플릿에 매개변수를 전달하여 HTML 페이지의 동적 렌더링을 지원합니다.
이 기사에서는 Express가 간단한 LikeExpress 클래스를 구현하여 미들웨어 등록, next 메커니즘 및 경로 처리를 구현하는 방법을 분석합니다.
Express 분석
먼저 두 가지 Express 코드 예제를 통해 제공하는 기능을 살펴보겠습니다.
Express 공식 웹사이트 Hello World 예제
const express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`); });
진입 파일 app.js 분석
다음은 express-generator
스캐폴딩에 의해 생성된 Express 프로젝트의 진입 파일 app.js
의 코드입니다.
// 일치하지 않는 경로로 인한 오류 처리 const createError = require('http-errors'); const express = require('express'); const path = require('path'); const indexRouter = require('./routes/index'); const usersRouter = require('./routes/users'); // `app`은 Express 인스턴스입니다. const app = express(); // 뷰 엔진 설정 app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // post 요청에서 JSON 형식 데이터를 파싱하고 `req` 객체에 `body` 필드를 추가합니다. app.use(express.json()); // post 요청에서 urlencoded 형식 데이터를 파싱하고 `req` 객체에 `body` 필드를 추가합니다. app.use(express.urlencoded({ extended: false })); // 정적 파일 처리 app.use(express.static(path.join(__dirname, 'public'))); // 최상위 경로 등록 app.use('/', indexRouter); app.use('/users', usersRouter); // 404 오류를 캐치하고 오류 처리기로 전달합니다. app.use((req, res, next) => { next(createError(404)); }); // 오류 처리 app.use((err, req, res, next) => { // 개발 환경에서 오류 메시지를 표시하도록 로컬 변수를 설정합니다. res.locals.message = err.message; // 환경 변수에 따라 전체 오류를 표시할지 여부를 결정합니다. 개발 환경에서는 표시하고, 프로덕션 환경에서는 숨깁니다. res.locals.error = req.app.get('env') === 'development'? err : {}; // 오류 페이지 렌더링 res.status(err.status || 500); res.render('error'); }); module.exports = app;
위의 두 코드 세그먼트에서 Express 인스턴스 app
에는 주로 세 가지 핵심 메서드가 있음을 알 수 있습니다.
app.use([path,] callback [, callback...])
: 미들웨어를 등록하는 데 사용됩니다. 요청 경로가 설정된 규칙과 일치하면 해당 미들웨어 함수가 실행됩니다.path
: 미들웨어 함수를 호출할 경로를 지정합니다.callback
: 콜백 함수는 다양한 형태를 취할 수 있습니다. 단일 미들웨어 함수, 쉼표로 구분된 일련의 미들웨어 함수, 미들웨어 함수 배열 또는 위의 모든 조합이 될 수 있습니다.
app.get()
및app.post()
: 이러한 메서드는use()
와 유사하며 미들웨어 등록에도 사용됩니다. 그러나 HTTP 요청 메서드에 바인딩됩니다. 해당 HTTP 요청 메서드가 사용되는 경우에만 관련 미들웨어 등록이 트리거됩니다.app.listen()
: httpServer를 생성하고server.listen()
에 필요한 매개변수를 전달하는 역할을 합니다.
코드 구현
Express 코드의 기능 분석을 기반으로 Express 구현은 다음 세 가지 사항에 중점을 두고 있다는 것을 알 수 있습니다.
- 미들웨어 함수 등록 프로세스.
- 미들웨어 함수의 핵심 next 메커니즘.
- 경로 처리, 경로 일치에 중점을 둡니다.
이러한 점을 기반으로 아래에 간단한 LikeExpress 클래스를 구현합니다.
1. 클래스의 기본 구조
먼저 이 클래스가 구현해야 하는 주요 메서드를 명확히 합니다.
use()
: 일반 미들웨어 등록을 구현합니다.get()
및post()
: HTTP 요청과 관련된 미들웨어 등록을 구현합니다.listen()
: 기본적으로 httpServer의listen()
함수입니다. 이 클래스의listen()
함수에서는 httpServer가 생성되고, 매개변수를 통해 전달되고, 요청을 수신하고, 콜백 함수(req, res) => {}
가 실행됩니다.
기본 Node httpServer 사용법을 검토합니다.
const http = require("http"); const server = http.createServer((req, res) => { res.end("hello"); }); server.listen(3003, "127.0.0.1", () => { console.log("node service started successfully"); });
따라서 LikeExpress 클래스의 기본 구조는 다음과 같습니다.
const http = require('http'); class LikeExpress { constructor() {} use() {} get() {} post() {} // httpServer 콜백 함수 callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; }; } listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress(); };
2. 미들웨어 등록
app.use([path,] callback [, callback...])
에서 미들웨어는 함수 배열 또는 단일 함수일 수 있음을 알 수 있습니다. 구현을 단순화하기 위해 미들웨어를 함수 배열로 균일하게 처리합니다. LikeExpress 클래스에서 세 가지 메서드 use()
, get()
및 post()
는 모두 미들웨어 등록을 구현할 수 있습니다. 트리거되는 미들웨어만 요청 메서드에 따라 다릅니다. 따라서 다음을 고려합니다.
- 일반 미들웨어 등록 함수 추상화.
- 이러한 세 가지 메서드에 대한 미들웨어 함수 배열을 생성하여 다른 요청에 해당하는 미들웨어를 저장합니다.
use()
는 모든 요청에 대한 일반 미들웨어 등록 메서드이므로use()
미들웨어를 저장하는 배열은get()
및post()
에 대한 배열의 합집합입니다.
미들웨어 큐 배열
미들웨어 배열은 클래스의 메서드가 쉽게 액세스할 수 있도록 공용 영역에 배치해야 합니다. 따라서 미들웨어 배열을 constructor()
생성자 함수에 넣습니다.
constructor() { // 저장된 미들웨어 목록 this.routes = { all: [], // 일반 미들웨어 get: [], // get 요청에 대한 미들웨어 post: [], // post 요청에 대한 미들웨어 }; }
미들웨어 등록 함수
미들웨어 등록은 해당 미들웨어 배열에 미들웨어를 저장하는 것을 의미합니다. 미들웨어 등록 함수는 들어오는 매개변수를 파싱해야 합니다. 첫 번째 매개변수는 경로 또는 미들웨어일 수 있으므로 먼저 경로인지 확인해야 합니다. 그렇다면 있는 그대로 출력하고, 그렇지 않으면 기본값은 루트 경로이고 나머지 미들웨어 매개변수를 배열로 변환합니다.
register(path) { const info = {}; // 첫 번째 매개변수가 경로인 경우 if (typeof path === "string") { info.path = path; // 두 번째 매개변수부터 배열로 변환하여 미들웨어 배열에 저장합니다. info.stack = Array.prototype.slice.call(arguments, 1); } else { // 첫 번째 매개변수가 경로가 아니면 기본값은 루트 경로이고 모든 경로가 실행됩니다. info.path = '/'; info.stack = Array.prototype.slice.call(arguments, 0); } return info; }
use()
, get()
및 post()
구현
일반 미들웨어 등록 함수 register()
를 사용하면 해당 배열에 미들웨어를 저장하기만 하면 use()
, get()
및 post()
를 쉽게 구현할 수 있습니다.
use() { const info = this.register.apply(this, arguments); this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); }
3. 경로 일치 처리
등록 함수의 첫 번째 매개변수가 경로인 경우 요청 경로가 경로와 일치하거나 하위 경로인 경우에만 해당 미들웨어 함수가 트리거됩니다. 따라서 후속 callback()
함수가 실행할 수 있도록 요청 메서드 및 요청 경로에 따라 일치하는 경로의 미들웨어 배열을 추출하는 경로 일치 함수가 필요합니다.
match(method, url) { let stack = []; // 브라우저의 내장 아이콘 요청 무시 if (url === "/favicon") { return stack; } // 경로 가져오기 let curRoutes = []; curRoutes = curRoutes.concat(this.routes.all); curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach((route) => { if (url.indexOf(route.path) === 0) { stack = stack.concat(route.stack); } }); return stack; }
그런 다음 httpServer의 콜백 함수 callback()
에서 실행해야 하는 미들웨어를 추출합니다.
callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; const url = req.url; const method = req.method.toLowerCase(); const resultList = this.match(method, url); this.handle(req, res, resultList); }; }
4. next 메커니즘 구현
Express 미들웨어 함수의 매개변수는 req
, res
및 next
이며 여기서 next
는 함수입니다. 이를 호출해야만 ES6 Generator의 next()
와 유사하게 미들웨어 함수가 순서대로 실행될 수 있습니다. 구현에서는 다음과 같은 요구 사항으로 next()
함수를 작성해야 합니다.
- 매번 미들웨어 큐 배열에서 하나의 미들웨어를 순서대로 추출합니다.
- 추출된 미들웨어에
next()
함수를 전달합니다. 미들웨어 배열은 공용이므로next()
가 실행될 때마다 배열의 첫 번째 미들웨어 함수가 꺼내져 실행되므로 미들웨어가 순차적으로 실행되는 효과를 얻을 수 있습니다.
// 핵심 next 메커니즘 handle(req, res, stack) { const next = () => { const middleware = stack.shift(); if (middleware) { middleware(req, res, next); } }; next(); }
Express 코드
const http = require('http'); const slice = Array.prototype.slice; class LikeExpress { constructor() { // 저장된 미들웨어 목록 this.routes = { all: [], get: [], post: [], }; } register(path) { const info = {}; // 첫 번째 매개변수가 경로인 경우 if (typeof path === "string") { info.path = path; // 두 번째 매개변수부터 배열로 변환하여 스택에 저장합니다. info.stack = slice.call(arguments, 1); } else { // 첫 번째 매개변수가 경로가 아니면 기본값은 루트 경로이고 모든 경로가 실행됩니다. info.path = '/'; info.stack = slice.call(arguments, 0); } return info; } use() { const info = this.register.apply(this, arguments); this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); } match(method, url) { let stack = []; // 브라우저의 내장 아이콘 요청 if (url === "/favicon") { return stack; } // 경로 가져오기 let curRoutes = []; curRoutes = curRoutes.concat(this.routes.all); curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach((route) => { if (url.indexOf(route.path) === 0) { stack = stack.concat(route.stack); } }); return stack; } // 핵심 next 메커니즘 handle(req, res, stack) { const next = () => { const middleware = stack.shift(); if (middleware) { middleware(req, res, next); } }; next(); } callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; const url = req.url; const method = req.method.toLowerCase(); const resultList = this.match(method, url); this.handle(req, res, resultList); }; } listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress(); };
Leapcell: 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼
마지막으로 Express 배포에 매우 적합한 플랫폼인 Leapcell을 소개합니다.
Leapcell은 다음과 같은 특징을 가진 서버리스 플랫폼입니다.
1. 다국어 지원
- JavaScript, Python, Go 또는 Rust로 개발하십시오.
2. 무료로 무제한 프로젝트 배포
- 사용량에 대해서만 지불하십시오. 요청이나 요금이 없습니다.
3. 최고의 비용 효율성
- 사용한 만큼만 지불하고 유휴 요금이 없습니다.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
4. 간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
5. 간편한 확장성 및 고성능
- 쉬운 고 동시성 처리를 위한 자동 확장.
- 운영 오버헤드가 제로이므로 구축에만 집중하십시오.
Leapcell Twitter: https://x.com/LeapcellHQ