본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

Express 라우팅과 CRUD API

작성 2026. 10. 13.추가
목차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/:idfind로 한 건 조회200, 없으면 404
POST/catsreq.body를 배열에 push201
PUT/cats/:idfindIndex 위치를 req.body로 통째 교체200, 없으면 404
PATCH/cats/:id기존 값 위에 바뀐 필드만 덮어쓰기200, 없으면 404
DELETE/cats/:idfindIndex 위치를 splice200, 없으면 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 미들웨어, 싱글톤·서비스 패턴, 웹 앱의 요청·응답 구조

댓글

GitHub 계정으로 댓글을 남길 수 있어요.