iBetter Books
수정

Ch 01. Express + TypeScript 프로젝트 구성

서버 프로젝트는 프론트엔드와 출발점이 다릅니다. Vite 같은 올인원 도구가 없습니다. 직접 TypeScript 컴파일러를 설정하고, 개발 서버를 연결하고, 타입 선언 파일을 설치해야 합니다. 번거로워 보이지만 한 번 구성해두면 이후 모든 것이 편해집니다.

프로젝트 초기화

빈 폴더를 만들고 npm 프로젝트로 초기화합니다.

mkdir todo-apicd todo-apinpm init -y

이제 필요한 패키지를 설치합니다. Express 5는 현재 @latest 태그를 달고 있습니다.

# 런타임 의존성npm install express# 개발 의존성npm install -D typescript @types/node @types/express tsx nodemon

@types/express는 Express의 TypeScript 타입 선언 패키지입니다. Express 자체는 JavaScript로 작성되어 있어서, 별도로 타입 선언을 설치해야 TypeScript가 Express 객체를 이해합니다.

tsx는 TypeScript 파일을 빌드 없이 직접 실행해주는 도구입니다. ts-node보다 훨씬 빠르고, ESM과 CJS를 모두 지원합니다.

tsconfig.json 서버용 설정

프론트엔드와 백엔드의 tsconfig는 목적이 다릅니다. 브라우저 환경이 아니라 Node.js 환경이기 때문에 libtarget이 달라집니다.

// 새 파일: tsconfig.json{  "compilerOptions": {    "target": "ES2022",    "module": "CommonJS",    "lib": ["ES2022"],    "outDir": "./dist",    "rootDir": "./src",    "strict": true,    "esModuleInterop": true,    "skipLibCheck": true,    "forceConsistentCasingInFileNames": true,    "resolveJsonModule": true,    "declaration": true,    "declarationMap": true,    "sourceMap": true  },  "include": ["src/**/*"],  "exclude": ["node_modules", "dist"]}

주요 옵션을 하나씩 살펴봅니다.

target: "ES2022" — Node.js 18 이상은 ES2022를 완전히 지원합니다. 최신 문법을 트랜스파일 없이 사용할 수 있습니다.

module: "CommonJS" — Node.js의 기본 모듈 시스템입니다. Express와 대부분의 서버 패키지가 CJS를 기준으로 만들어졌습니다.

strict: true — 타입 안전한 서버를 만들려면 반드시 켜야 합니다. strictNullChecks, noImplicitAny 등이 모두 활성화됩니다.

declaration: true.d.ts 파일을 생성합니다. 나중에 타입 패키지를 프론트엔드와 공유할 때 필요합니다.

프로젝트 구조 설계

todo-api/
├── src/
│   ├── routes/
│   │   └── todos.ts        # Todo 라우트
│   ├── middleware/
│   │   └── errorHandler.ts # 에러 처리 미들웨어
│   ├── models/
│   │   └── todo.ts         # 타입 정의
│   ├── db/
│   │   └── prisma.ts       # Prisma 클라이언트
│   └── index.ts            # 앱 진입점
├── prisma/
│   └── schema.prisma       # 데이터베이스 스키마
├── package.json
└── tsconfig.json

서버 진입점 작성

// 새 파일: src/index.tsimport express from 'express';const app = express();const PORT = process.env.PORT ?? 3000;app.use(express.json());app.get('/health', (_req, res) => {  res.json({ status: 'ok', timestamp: new Date().toISOString() });});app.listen(PORT, () => {  console.log(`서버가 http://localhost:${PORT} 에서 실행 중입니다.`);});export default app;

express.json() 미들웨어는 Content-Type: application/json 요청의 본문을 파싱해서 req.body에 담아줍니다. 이것이 없으면 POST/PUT 요청에서 req.body가 항상 undefined입니다.

?? 연산자는 Nullish Coalescing입니다. process.env.PORTundefined이거나 null일 때만 오른쪽 값을 씁니다. ||와 달리 0이나 빈 문자열은 그대로 통과시킵니다.

package.json 스크립트 설정

// 수정: package.json{  "name": "todo-api",  "version": "1.0.0",  "scripts": {    "dev": "nodemon --watch src --ext ts --exec tsx src/index.ts",    "build": "tsc",    "start": "node dist/index.js",    "typecheck": "tsc --noEmit"  },  "dependencies": {    "express": "^5.0.0"  },  "devDependencies": {    "@types/express": "^5.0.0",    "@types/node": "^22.0.0",    "nodemon": "^3.0.0",    "tsx": "^4.0.0",    "typescript": "^5.0.0"  }}

dev 스크립트를 분석합니다.

  • --watch srcsrc 폴더를 감시합니다.
  • --ext ts.ts 파일이 변경될 때 재시작합니다.
  • --exec tsx src/index.tstsx로 TypeScript 파일을 직접 실행합니다.

typecheck 스크립트는 컴파일 없이 타입 검사만 합니다. CI/CD에서 빠르게 타입 오류를 검출할 때 유용합니다.

개발 서버 실행 확인

npm run dev

터미널에 다음이 출력되면 성공입니다.

[nodemon] starting `tsx src/index.ts`
서버가 http://localhost:3000 에서 실행 중입니다.

src/index.ts 파일을 수정하고 저장하면 nodemon이 자동으로 서버를 재시작합니다. 콘솔에 [nodemon] restarting due to changes... 메시지가 보이면 핫 리로드가 동작하는 것입니다.

헬스 체크 엔드포인트를 브라우저나 curl로 확인합니다.

curl http://localhost:3000/health# {"status":"ok","timestamp":"2026-04-25T00:00:00.000Z"}

환경 변수 타이핑

process.env의 값은 항상 string | undefined입니다. 이를 그냥 쓰면 타입 오류가 생깁니다. 환경 변수를 한 곳에서 검증하고 타입을 확정하는 파일을 만들면 편합니다.

// 새 파일: src/config.tsfunction requireEnv(key: string): string {  const value = process.env[key];  if (!value) {    throw new Error(`환경 변수 ${key}가 설정되지 않았습니다.`);  }  return value;}export const config = {  port: parseInt(process.env.PORT ?? '3000', 10),  nodeEnv: (process.env.NODE_ENV ?? 'development') as 'development' | 'production' | 'test',  databaseUrl: requireEnv('DATABASE_URL'),} as const;

requireEnv는 값이 없으면 즉시 서버를 종료합니다. 서버가 뜨는 순간 필수 환경 변수를 모두 검증하는 것이 좋습니다. 배포 후 한참 지나서 특정 기능을 쓸 때 오류가 나는 것보다, 시작 단계에서 바로 실패하는 편이 훨씬 낫습니다.

as constconfig 객체를 읽기 전용으로 만듭니다. nodeEnvstring이 아니라 세 가지 리터럴 타입의 유니온으로 좁혀집니다.