Node.js와 Express.js 입문
Node.js 런타임과 Express 프레임워크의 역할을 이해하고 라우트와 미들웨어를 가진 첫 HTTP 서버를 실행합니다.
앞 절에서는 웹의 근본적인 작동 원리인 클라이언트-서버 모델과 HTTP 통신 방식에 대해 학습했습니다.
프론트엔드가 사용자에게 보이는 부분을 담당한다면, 백엔드는 보이지 않는 곳에서 데이터를 처리하고 비즈니스 로직을 수행하며, 클라이언트에게 필요한 정보를 제공하는 역할을 합니다.
이번 장부터는 이러한 백엔드 서버를 직접 구축하는 방법을 배웁니다.
특히, 프론트엔드 개발에 사용했던 JavaScript 언어를 백엔드에서도 사용할 수 있게 해주는 Node.js와, Node.js 기반의 웹 애플리케이션 프레임워크인 Express.js를 중심으로 서버 개발의 기초를 다질 것입니다.
JavaScript 하나로 프론트엔드와 백엔드를 모두 개발할 수 있는 Full-Stack 개발의 첫걸음을 내딛는 시간입니다.
Node.js 런타임과 비동기 I/O 경계 — 상태 추적
JS 호출·I/O 위임·완료 통지·콜백 실행의 주체와 대기 상태가 달라진다. JS 스택이 I/O 완료를 계속 붙들고 있다는 오해를 줄이며 실제 libuv phase 전체는 생략한다.
RUNTIME BOUNDARY
I/O를 맡기고 완료되면 JS 콜백으로
Node API가 I/O를 OS나 libuv worker pool에 맡깁니다. 이벤트 루프는 완료 알림에 따라 JS 콜백을 실행하며, 긴 동기 코드는 이 처리를 지연시킬 수 있습니다.
긴 CPU 연산은 별도로 분리
worker_threads는 별도 JS 스레드에서 CPU 작업을 실행하고 메시지로 결과를 전달합니다. libuv I/O pool과는 역할이 다릅니다.
JS 호출 스택을 오래 점유하면 콜백 처리가 지연됩니다. “JS 콜백을 한 번에 하나 실행한다”는 “Node 프로세스에 스레드가 하나뿐이다”라는 뜻이 아닙니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | worker_threads는 별도 JS 스레드에서 CPU 작업을 실행하고 메시지로 결과를 전달합니다. libuv I/O pool과는 역할이 다릅니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 동기 실행 | JS stack: 현재 함수 / IO: 호스트에 위임 | Node JavaScript가 IO를 등록한다 | IO 등록은 callback 완료가 아니다 |
| 호스트 작업 | JS stack: 다른 작업 가능 / IO: 진행/완료 준비 | JavaScript는 반환하고 IO 완료를 기다린다 | 긴 IO를 기다리는 동안 현재 함수가 계속 실행되는 것은 아니다 |
| callback 실행 | JS stack: callback / IO: 결과 사용 | 실행 가능해진 callback이 JS stack에서 이어진다 | callback 계산은 다시 JavaScript 실행이며 phase 전체 모델은 생략한다 |
Node.js 런타임과 비동기 I/O 경계 — 상태 추적
JS 호출·I/O 위임·완료 통지·콜백 실행의 주체와 대기 상태가 달라진다. JS 스택이 I/O 완료를 계속 붙들고 있다는 오해를 줄이며 실제 libuv phase 전체는 생략한다.
RUNTIME BOUNDARY
I/O를 맡기고 완료되면 JS 콜백으로
Node API가 I/O를 OS나 libuv worker pool에 맡깁니다. 이벤트 루프는 완료 알림에 따라 JS 콜백을 실행하며, 긴 동기 코드는 이 처리를 지연시킬 수 있습니다.
긴 CPU 연산은 별도로 분리
worker_threads는 별도 JS 스레드에서 CPU 작업을 실행하고 메시지로 결과를 전달합니다. libuv I/O pool과는 역할이 다릅니다.
JS 호출 스택을 오래 점유하면 콜백 처리가 지연됩니다. “JS 콜백을 한 번에 하나 실행한다”는 “Node 프로세스에 스레드가 하나뿐이다”라는 뜻이 아닙니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | worker_threads는 별도 JS 스레드에서 CPU 작업을 실행하고 메시지로 결과를 전달합니다. libuv I/O pool과는 역할이 다릅니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 동기 실행 | JS stack: 현재 함수 / IO: 호스트에 위임 | Node JavaScript가 IO를 등록한다 | IO 등록은 callback 완료가 아니다 |
| 호스트 작업 | JS stack: 다른 작업 가능 / IO: 진행/완료 준비 | JavaScript는 반환하고 IO 완료를 기다린다 | 긴 IO를 기다리는 동안 현재 함수가 계속 실행되는 것은 아니다 |
| callback 실행 | JS stack: callback / IO: 결과 사용 | 실행 가능해진 callback이 JS stack에서 이어진다 | callback 계산은 다시 JavaScript 실행이며 phase 전체 모델은 생략한다 |
Node.js: 서버 사이드 JavaScript 런타임
우리는 지금까지 JavaScript를 웹 브라우저에서 실행되는 클라이언트 사이드 스크립트 언어로 사용했습니다.
하지만 Node.js는 Google Chrome의 V8 JavaScript 엔진을 기반으로 구축된 JavaScript 런타임으로, 브라우저 환경이 아닌 서버 환경에서도 JavaScript 코드를 실행할 수 있게 해줍니다.
Node.js의 특징 및 장점
- 비동기 논블로킹 I/O: 이 예제의 JavaScript 콜백은 하나의 Event Loop에서 실행하고, I/O는 OS와 일부 작업의 libuv 스레드 풀 등의 지원을 받습니다. 따라서 Node.js 전체가 한 스레드만 사용하는 것은 아닙니다. I/O가 많은 서버에 유리하지만 긴 동기 JavaScript 작업은 그 Event Loop를 막을 수 있습니다.
- JavaScript 언어 사용: 프론트엔드와 백엔드 모두 JavaScript로 개발할 수 있어, 풀스택(Full-Stack) 개발이 용이하고 개발자들이 언어 전환에 대한 부담 없이 개발할 수 있습니다.
- 방대한 생태계 (NPM): Node.js는 NPM(Node Package Manager)이라는 거대한 패키지 생태계를 가지고 있습니다. 수많은 오픈소스 라이브러리와 모듈을 쉽게 설치하고 사용할 수 있어 개발 생산성을 극대화합니다.
- 빠른 개발 속도: 경량 프레임워크와 빠른 시작 속도 덕분에 프로토타이핑 및 MVP(Minimum Viable Product) 개발에 유리합니다.
Node.js 설치 확인
Node.js를 사용하려면 먼저 시스템에 Node.js가 설치되어 있어야 합니다.
node -v # Node.js 버전 확인
npm -v # npm 버전 확인만약 설치되어 있지 않다면, Node.js 공식 웹사이트에서 LTS(Long Term Support) 버전을 다운로드하여 설치할 수 있습니다.
아래의 본문 파싱 동작은 Express 5 기준으로 설명합니다. Express 5는 Node.js 18 이상을 요구합니다. require()를 쓰는 app.js는 이 절에서 새로 만든 CommonJS 프로젝트를 전제로 합니다. 기존 프로젝트에 "type": "module"이 있다면 CommonJS 예제를 그대로 app.js에 붙여 넣는 조건과 다르므로 모듈 형식부터 맞춰야 합니다.
Express.js: Node.js 프레임워크
Node.js만으로도 웹 서버를 만들 수 있지만, HTTP 요청 처리, 라우팅, 미들웨어 관리 등 웹 애플리케이션 개발에 필요한 복잡한 작업을 직접 구현하는 것은 비효율적입니다.
Express.js는 Node.js를 위한 빠르고 개방적이며 최소한의(minimalist) 웹 애플리케이션 프레임워크입니다.
Express.js는 웹 개발에 필요한 기능들을 미리 구현해 놓은 모듈 집합을 제공하여, 개발자가 더 쉽고 빠르게 백엔드 서버를 구축할 수 있도록 돕습니다.
Express.js의 주요 기능 및 특징
- 라우팅 (Routing): URL 경로와 HTTP 메서드(GET, POST 등)에 따라 적절한 함수를 실행하여 요청을 처리하는 기능을 제공합니다.
- 미들웨어 (Middleware): 요청-응답 주기 동안 다양한 작업을 수행하는 함수들을 연결할 수 있는 구조를 제공합니다. (예: 요청 로깅, 인증, 데이터 파싱 등)
- 템플릿 엔진 지원: 동적으로 HTML 페이지를 생성할 수 있도록 Pug(구 Jade), EJS, Handlebars 등 다양한 템플릿 엔진과의 통합을 지원합니다.
- HTTP 유틸리티 메서드: 요청 및 응답 객체(
req,res)에 편리한 HTTP 유틸리티 메서드를 추가하여 개발을 용이하게 합니다. - 뛰어난 확장성: 필요한 기능만 추가하여 사용할 수 있는 최소한의 프레임워크이므로, 프로젝트의 필요에 따라 유연하게 기능을 확장할 수 있습니다.
첫 번째 Express.js 서버 구축하기
Node.js와 Express.js 입문에서는 데이터 위치, 요청 계약, 응답 처리 기준을 정리합니다.
Express 미들웨어 요청 처리 파이프라인 — 상태 추적
본문 POST 요청이 parser→guards→route→handler를 지나며 req 상태와 응답 여부가 바뀐다. next와 응답 종료/오류 전달을 실제 요청 한 건으로 추적하면 중복 응답을 예방한다.
FLOWCHART · B99 WEB FUNDAMENTALS
Express 미들웨어 요청 처리 파이프라인
미들웨어는 등록 순서대로 실행됩니다. next()는 다음 처리로, next(err)는 오류 처리기로 넘어갑니다. 응답을 보냈다면 후속 처리를 멈춥니다. 인증·검증은 본문 예제에 보강할 설계 단계입니다.
404는 라우트 끝까지 응답하지 않은 요청을 처리하는 일반 미들웨어입니다. 오류 처리기는 (err, req, res, next)를 받으며 라우트 뒤에 등록합니다. Express 5는 반환된 Promise 거부도 전달하지만, 별도 비동기 콜백의 오류는 직접 next(err)로 전달해야 합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | 미들웨어는 등록 순서대로 실행됩니다. next()는 다음 처리로, next(err)는 오류 처리기로 넘어갑니다. 응답을 보냈다면 후속 처리를 멈춥니다. 인증·검증은 본문 예제에 보강할 설계 단계입니다. |
| 전체 관계·조건 설명 | HTTP 요청은 parser와 guards를 거쳐 method와 path를 비교한다. 일치하면 업무 처리와 성공 응답으로, NO MATCH면 404로 향한다. guards의 NEXT(ERROR)는 별도 오류 처리기로 향한다. 하나의 응답을 보내고 요청을 종료한다. |
| 원도표 설명 | 404는 라우트 끝까지 응답하지 않은 요청을 처리하는 일반 미들웨어입니다. 오류 처리기는 (err, req, res, next)를 받으며 라우트 뒤에 등록합니다. Express 5는 반환된 Promise 거부도 전달하지만, 별도 비동기 콜백의 오류는 직접 next(err)로 전달해야 합니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 본문 파싱 | req.body: 파싱 결과 / 응답: 미전송 | Express parser가 요청 body를 준비한다 | 형식 오류는 다음 handler로 성공 값처럼 넘기지 않는다 |
| guard와 route | 실행: middleware/guard/route / 응답: 미전송 또는 거절 | 조건을 만족한 요청만 handler로 전달한다 | next와 error 경로가 후속 실행을 결정한다 |
| 응답 종료 | 응답: 전송 완료 / handler: 종료 또는 지정 next | 한 응답을 보내면 후속 전송을 반복하지 않는다 | 처리 완료와 다음 middleware 호출은 자동으로 같은 뜻이 아니다 |
Express 미들웨어 요청 처리 파이프라인 — 상태 추적
본문 POST 요청이 parser→guards→route→handler를 지나며 req 상태와 응답 여부가 바뀐다. next와 응답 종료/오류 전달을 실제 요청 한 건으로 추적하면 중복 응답을 예방한다.
FLOWCHART · B99 WEB FUNDAMENTALS
Express 미들웨어 요청 처리 파이프라인
미들웨어는 등록 순서대로 실행됩니다. next()는 다음 처리로, next(err)는 오류 처리기로 넘어갑니다. 응답을 보냈다면 후속 처리를 멈춥니다. 인증·검증은 본문 예제에 보강할 설계 단계입니다.
404는 라우트 끝까지 응답하지 않은 요청을 처리하는 일반 미들웨어입니다. 오류 처리기는 (err, req, res, next)를 받으며 라우트 뒤에 등록합니다. Express 5는 반환된 Promise 거부도 전달하지만, 별도 비동기 콜백의 오류는 직접 next(err)로 전달해야 합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 원도표 설명 | 미들웨어는 등록 순서대로 실행됩니다. next()는 다음 처리로, next(err)는 오류 처리기로 넘어갑니다. 응답을 보냈다면 후속 처리를 멈춥니다. 인증·검증은 본문 예제에 보강할 설계 단계입니다. |
| 전체 관계·조건 설명 | HTTP 요청은 parser와 guards를 거쳐 method와 path를 비교한다. 일치하면 업무 처리와 성공 응답으로, NO MATCH면 404로 향한다. guards의 NEXT(ERROR)는 별도 오류 처리기로 향한다. 하나의 응답을 보내고 요청을 종료한다. |
| 원도표 설명 | 404는 라우트 끝까지 응답하지 않은 요청을 처리하는 일반 미들웨어입니다. 오류 처리기는 (err, req, res, next)를 받으며 라우트 뒤에 등록합니다. Express 5는 반환된 Promise 거부도 전달하지만, 별도 비동기 콜백의 오류는 직접 next(err)로 전달해야 합니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 본문 파싱 | req.body: 파싱 결과 / 응답: 미전송 | Express parser가 요청 body를 준비한다 | 형식 오류는 다음 handler로 성공 값처럼 넘기지 않는다 |
| guard와 route | 실행: middleware/guard/route / 응답: 미전송 또는 거절 | 조건을 만족한 요청만 handler로 전달한다 | next와 error 경로가 후속 실행을 결정한다 |
| 응답 종료 | 응답: 전송 완료 / handler: 종료 또는 지정 next | 한 응답을 보내면 후속 전송을 반복하지 않는다 | 처리 완료와 다음 middleware 호출은 자동으로 같은 뜻이 아니다 |
이제 직접 Node.js와 Express.js를 사용하여 간단한 웹 서버를 만들어 보겠습니다.
mkdir my-express-server
cd my-express-server
npm init -ynpm init -y 명령은 package.json 파일을 기본값으로 생성합니다.
npm install expressapp.js 또는 server.js)프로젝트 루트 디렉터리에 app.js 파일을 생성하고 다음 코드를 작성합니다.
// 1. Express 모듈을 임포트합니다.
const express = require('express');
// 2. Express 애플리케이션 인스턴스를 생성합니다.
const app = express();
// 3. 서버가 들을 포트 번호를 정의합니다.
// process.env.PORT는 환경 변수가 설정되어 있을 경우 해당 포트를 사용하고,
// 그렇지 않으면 3000번 포트를 기본값으로 사용합니다.
const port = process.env.PORT || 3000;
// 4. 미들웨어 설정: JSON 요청 본문 파싱 (클라이언트가 JSON 데이터를 보낼 때 필요)
app.use(express.json());
// 5. 라우트(Route) 정의: GET 요청 처리
// '/' 경로는 웹사이트의 루트(홈)를 의미합니다.
app.get('/', (req, res) => {
// 클라이언트에게 "Hello, World! Welcome to my Express server!" 메시지를 응답합니다.
res.send('Hello, World! Welcome to my Express server!');
});
// 6. 새로운 라우트 정의: /api/greeting 경로로 GET 요청 처리
// 쿼리 파라미터 'name'을 받아 사용합니다.
app.get('/api/greeting', (req, res) => {
const queryName = req.query.name; // req.query로 쿼리 파라미터 접근
const name = typeof queryName === 'string'
? queryName || '방문자'
: '방문자';
res.type('text/plain').send(`안녕하세요, ${name}님! Express API에 오신 것을 환영합니다.`);
});
// 7. 새로운 라우트 정의: POST 요청 처리 (데이터 받기)
app.post('/api/data', (req, res) => {
const receivedData = req.body; // req.body로 요청 본문 (JSON) 접근
console.log('클라이언트로부터 받은 데이터:', receivedData);
res.status(200).json({
message: '데이터가 성공적으로 수신되었습니다!',
yourData: receivedData
});
});
// 8. 서버 시작: 정의된 포트에서 요청을 대기합니다.
app.listen(port, () => {
console.log(`서버가 http://localhost:${port} 에서 실행 중입니다.`);
console.log(`http://localhost:${port}/api/greeting?name=김철수 로 접속해보세요.`);
console.log(`POST 요청 테스트: http://localhost:${port}/api/data`);
});이 인사말은 HTML 페이지가 아닌 텍스트 응답이므로 text/plain을 명시합니다. name은 문자열일 때만 사용하고, 없거나 빈 문자열이면 기존처럼 방문자로 응답합니다.
터미널에서 다음 명령어를 실행합니다.
node app.js서버가 성공적으로 실행되면 터미널에 서버가 http://localhost:3000 에서 실행 중입니다.와 같은 메시지가 출력됩니다.
- 웹 브라우저를 열고
http://localhost:3000으로 접속합니다. Hello, World! Welcome to my Express server! 메시지가 보일 것입니다. http://localhost:3000/api/greeting?name=김철수로 접속하면 안녕하세요, 김철수님! Express API에 오신 것을 환영합니다. 메시지를 확인할 수 있습니다.- POST 요청 테스트: Postman, Insomnia 또는
Fetch API를 사용하는 프론트엔드 코드(9장에서 학습)를 통해http://localhost:3000/api/data로 JSON 데이터를 포함한 POST 요청을 보내보면 서버 터미널에 데이터가 로깅되고, 클라이언트에게 응답이 돌아올 것입니다.
POST 한 번을 입력부터 응답까지 따라가기
위 app.js를 실행한 터미널은 그대로 두고 요청 도구에서 메서드는 POST, URL은 http://localhost:3000/api/data, 헤더는 Content-Type: application/json, 본문 방식은 raw JSON으로 지정합니다. 주소창에 URL을 입력하는 동작은 GET 요청이므로 POST 핸들러를 시험하는 방법과 다릅니다.
본문에는 다음 값을 넣습니다.
{
"name": "Alice",
"age": 20
}curl을 사용할 수 있다면 별도의 터미널에서도 같은 요청을 만들 수 있습니다.
curl -i -X POST http://localhost:3000/api/data \
-H 'Content-Type: application/json' \
-d '{"name":"Alice","age":20}'이 입력이 express.json()을 통과하면 req.body는 name과 age를 가진 객체가 됩니다. /api/data의 POST 핸들러가 그 객체를 receivedData로 읽고, 서버 터미널에는 클라이언트로부터 받은 데이터:라는 라벨과 두 필드가 출력됩니다. 로그의 공백·따옴표 모양과 HTTP 응답 본문은 서로 다를 수 있습니다.
클라이언트에서 확인할 결과는 상태 200, JSON 응답, 그리고 다음 필드 값입니다. 들여쓰기는 읽기 위한 표시이며 실제 전송되는 JSON의 공백을 요구하는 것은 아닙니다.
{
"message": "데이터가 성공적으로 수신되었습니다!",
"yourData": {
"name": "Alice",
"age": 20
}
}요청의 Content-Type은 서버가 받은 요청 본문을 어떻게 해석해야 하는지 알려줍니다. 응답의 Content-Type은 클라이언트가 받은 본문을 어떻게 해석해야 하는지 알려주며, 이 예제에서는 res.json()이 설정합니다. Express의 JSON 파서는 요청 헤더가 맞는 본문만 해석하고, JSON 응답 메서드는 받은 값을 JSON으로 직렬화합니다.
앞의 파이프라인 그림에서 이 예제에 실제 있는 단계는 JSON parser → method·path에 맞는 handler → 응답입니다. 그림의 인증·인가·업무 입력 검증을 이 코드가 모두 구현한 것은 아닙니다. JSON 문법을 읽었다는 사실만으로 age가 양의 정수인지 같은 업무 조건까지 검증되지는 않습니다. 또한 /api/data는 받은 값을 돌려줄 뿐 배열이나 DB에 저장하지 않습니다.
Content-Type: text/plain으로 같은 JSON 문자열을 보내면 Express 5의 기본 JSON 파서가 건너뛰므로 req.body가 채워지지 않습니다. 이 핸들러는 본문 존재를 검사하지 않고 여전히 200을 보낼 수 있으므로 상태만 보지 말고 yourData도 확인해야 합니다. 반대로 application/json에 {"name":}처럼 깨진 JSON을 보내면 핸들러에 도착하기 전에 파싱 오류가 납니다. body-parser 오류 규약의 해당 상태는 400이며, 앞의 성공 JSON과 같은 오류 응답 모양을 보장하지는 않습니다.
독립 확인: 입력 하나를 바꿔 결과 예측하기
POST와 application/json은 유지하고 본문만 {"name":"Bob","age":21}로 바꿉니다. 서버 로그의 두 필드, 응답 상태, yourData를 먼저 적은 뒤 확인합니다. 이어 헤더만 text/plain으로 바꿨을 때 200만으로 수신 성공을 판정할 수 있는지도 설명해 보세요.
답: 정상 JSON 요청의 상태는 200이고 yourData는 {"name":"Bob","age":21}입니다. 로그에도 Bob과 21이 나타납니다. text/plain에서는 기본 JSON 파서가 실행되지 않으므로 같은 상태가 와도 두 필드가 수신된 객체로 응답에 포함된다고 볼 수 없습니다. 어느 경우도 DB 저장을 의미하지 않습니다.
서버가 실행된 뒤에는 클라이언트 요청이 Express 앱 안에서 어떤 순서로 이동하는지 이해하는 것이 중요합니다.
공통 미들웨어가 먼저 요청을 다듬고, 라우터가 경로를 고른 뒤, 핸들러가 실제 작업을 수행하고 응답을 돌려줍니다.
Express 서버는 작게 시작하더라도 요청 진입점, 공통 미들웨어, 라우트 핸들러, 개발 실행 방식을 분리해 두면 다음 기능을 붙이기 쉬워집니다.
Nodemon으로 개발 생산성 높이기 (선택)
Node.js 서버 코드를 수정할 때마다 서버를 껐다가 다시 켜야 하는 것은 매우 번거롭습니다.
Nodemon은 코드 변경을 감지하여 자동으로 Node.js 서버를 재시작해주는 유틸리티입니다.
npm install --save-dev nodemonpackage.json 스크립트 추가package.json 파일의 scripts 섹션에 start:dev 스크립트를 추가합니다.
{
"scripts": {
"start": "node app.js",
"start:dev": "nodemon app.js"
}
}기존 package.json의 scripts 객체에 start:dev를 병합하고 다른 스크립트는 유지합니다.
npm run start:dev이제 app.js 파일을 수정하고 저장하면, Nodemon이 자동으로 서버를 재시작하여 변경 사항을 즉시 반영합니다.
Express 서버를 확장할 때는 요청이 어떤 미들웨어와 라우트를 거치는지 먼저 그려 두면 파일 분리 기준도 명확해집니다.
Node.js·Express 기초 정리
핵심은 런타임과 프레임워크의 역할을 분리해 서버 구조를 단계적으로 확장하는 것입니다.
이번 장에서는 JavaScript 기반의 백엔드 개발을 위한 핵심 기술 스택인 Node.js와 Express.js의 기초를 탄탄하게 다졌습니다.
이번 절에서는 Node.js를 브라우저 밖에서 JavaScript를 실행하는 런타임으로 정리하고, 비동기 논블로킹 I/O와 NPM 생태계의 역할을 확인했습니다.
Express.js는 라우팅, 미들웨어, HTTP 유틸리티를 제공하는 Node.js 기반 웹 프레임워크입니다.
직접 app.js 파일을 작성하여 기본적인 GET/POST 요청을 처리하는 Express 서버를 구축하고 실행해보았으며, Nodemon을 사용하여 개발 생산성을 높이는 방법도 익혔습니다.
이제는 프론트엔드 개발에서 사용했던 JavaScript 언어로 백엔드 서버까지 구축할 수 있는 풀스택 개발의 기반을 마련했습니다.