목차4개 항목
Express는 Node.js에서 가장 널리 쓰이는 최소한의 웹 프레임워크다. NestJS도 기본적으로 Express 위에서 동작하므로, Express로 라우팅·CRUD를 직접 만들어 보면 NestJS가 무엇을 대신 해 주는지 분명해진다.
개발 환경과 Hello World
npm i express
npm i -D @types/express typescript-D(devDependencies)로 설치한 패키지는 개발·빌드 때만 필요하고 운영 의존성에는 포함되지 않는다. 타입 정의(@types/*)가 대표적이다.npm i express는 Express 5를 설치한다(2025년 3월부터 npm 기본 버전). 라우트 경로 문법이 4와 조금 달라, 와일드카드는/*splat처럼 이름을 붙인다.npm run start:dev같은 스크립트에tsc --watch나tsx watch를 걸어 두면 코드가 바뀔 때마다 자동으로 다시 컴파일·실행된다.npm run start는prestart에서tsconfig.json기준으로dist/에 빌드한 뒤node dist/app.js를 실행하는 식으로 구성한다.
// app.ts
const app = express(); // 서버 역할을 하는 인스턴스
app.get('/', (req, res) => res.send('Hello World!')); // GET / 라우트
app.listen(8000);Router로 라우트 분리하기
app.ts 한 파일에 모든 경로를 쓰면 금방 복잡해진다. 도메인별로 Router 인스턴스를 만들어 분리하고 app.use()로 연결한다.
src/
├── app.ts
├── app.model.ts # 목업 데이터(CatType[])
└── cats/
├── cats.route.ts # 라우팅
└── cats.service.ts # 비즈니스 로직 (→ 서비스 패턴)데이터는 CatType = { id, name, age, species, isCute, friends } 배열을 메모리에 둔 목업이다.
// app.ts
import express from 'express';
import catsRouter from './cats/cats.route';
const app = express();
app.use(express.json()); // JSON 바디 파싱 (없으면 req.body가 undefined)
app.use(catsRouter);
app.listen(8000);CRUD 구현
응답은 { success, data } / { success, error } 형태로 통일하고, 상태 코드는 res.status()로 지정한다.
| 메서드 | 경로 | 처리 | 상태 코드 |
|---|---|---|---|
| GET | /cats | 전체 목록 반환 | 200 |
| GET | /cats/:id | find로 한 건 조회 | 200, 없으면 404 |
| POST | /cats | req.body를 배열에 push | 201 |
| PUT | /cats/:id | findIndex 위치를 req.body로 통째 교체 | 200, 없으면 404 |
| PATCH | /cats/:id | 기존 값 위에 바뀐 필드만 덮어쓰기 | 200, 없으면 404 |
| DELETE | /cats/:id | findIndex 위치를 splice | 200, 없으면 404 |
동적 라우팅은 :id 자리의 값을 req.params로 꺼낸다.
// cats/cats.route.ts
const router = Router();
router.get('/cats/:id', (req, res) => {
const cat = Cat.find((c) => c.id === req.params.id);
if (!cat) return res.status(404).send({ success: false, error: 'not found' });
res.status(200).send({ success: true, data: { cat } });
});PATCH는 스프레드로 기존 객체와 바디를 합친다. 뒤에 오는 req.body의 필드가 앞의 값을 덮어쓴다.
router.patch('/cats/:id', (req, res) => {
const index = Cat.findIndex((c) => c.id === req.params.id);
if (index === -1) return res.status(404).send({ success: false, error: 'not found' });
Cat[index] = { ...Cat[index], ...req.body };
res.status(200).send({ success: true, data: { cat: Cat[index] } });
});흔한 실수:
forEach((cat) => { cat = body })는 콜백의 지역 변수만 바꿀 뿐 배열 원소는 바뀌지 않는다.filter()도 새 배열을 만들 뿐 원본은 그대로다. 위처럼findIndex로 위치를 찾아 직접 대입·splice해야 실제로 반영된다. (DB를 붙이면 이 부분은 쿼리로 대체된다.)
핵심 정리
app.get/post/put/patch/delete(path, handler)로 메서드별 라우트를 정의한다.Router()로 도메인별 라우트를 분리하고app.use(router)로 연결한다.- POST/PUT/PATCH 바디를 읽으려면
express.json()미들웨어가 먼저 등록되어 있어야 한다.
관련 노트: REST API 설계, Express 미들웨어, 싱글톤·서비스 패턴, 웹 앱의 요청·응답 구조