이번에는 백엔드 API 구현에 앞서 데이터베이스를 연결하려고 합니다.
모던 웹 개발에서 백엔드 API 서버를 구축할 때 데이터베이스 연결과 에러 처리는 필수적인 요소입니다. 이번 글은 Express.js와 TypeScript를 기반으로 Prisma ORM을 사용하여 MySQL 데이터베이스에 연결하고, 에러 처리 미들웨어를 구현하는 과정을 설명하겠습니다.
@prisma/client
: 런타임에서 데이터베이스와 상호작용하는 클라이언트
prisma
: 개발 도구 (마이그레이션, 스키마 관리 등)
# 백엔드 디렉토리로 이동
cd apps/backend
# Prisma 관련 패키지 설치
npm install @prisma/client
npm install -D prisma
# 기타 필요한 패키지
npm install express cors dotenv cookie-parser
npm install -D @types/express @types/cors @types/cookie-parser
아래 명령어를 통해 prisma
디렉토리와 .env
파일을 생성합니다. prisma/schema.prisma
파일에 데이터베이스 스키마를 정의하고, .env
파일에 데이터베이스 연결 정보를 저장합니다.
npx prisma init
DATABASE_URL
: MySQL 데이터베이스 연결 문자열SERVER_PORT
: 서버 포트 번호# .env 파일
DATABASE_URL="mysql://username:password@localhost:3306/whattoeat"
SERVER_PORT=3000
generator
: Prisma 클라이언트 생성 설정datasource
: 데이터베이스 연결 설정model
: 데이터베이스 테이블 구조 정의 (Food 테이블 예시)// backend/prisma/schema.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["omitApi"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
createdAt DateTime @default(now())
@@map("users")
}
아래 명령어를 통해 스키마를 기반으로 실제 데이터베이스 테이블을 생성합니다.
npx prisma db push
// src/utils/prisma.util.ts
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'],
// 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력
errorFormat: 'pretty',
}); // PrismaClient 인스턴스를 생성합니다.
const connectDB = async () => {
try {
await prisma.$connect();
console.log('DB 연결에 성공했습니다.');
} catch (error) {
console.error('DB 연결에 실패했습니다.', error);
}
};
connectDB();
HTTP 상태 코드와 메시지를 포함하는 커스텀 에러 클래스입니다. 표준 Error 클래스를 확장하여 API 에러 처리에 특화된 기능을 제공합니다.
// src/utils/error-exception.util.ts
class HttpException extends Error {
status: number;
message: string;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.message = message;
}
}
export default HttpException;
(err, req, res, next)
를 가져야 함// src/middlewares/error-handler.middleware.ts
import { Request, Response, NextFunction } from 'express';
import HttpException from '../utils/error-exception.util';
export const errorHandlerMiddleware = (
err: HttpException,
req: Request,
res: Response,
next: NextFunction
) => {
const status = err.status || 500;
const message = err.message || 'Internal Server Error';
res.status(status).json({
status,
message,
});
};
dotenv.config()
를 최상단에 배치하여 환경변수를 먼저 로드prisma.$queryRaw
사용 (추후 삭제)// src/index.ts
import express, { NextFunction, Request, Response } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
// 환경변수를 다른 모듈보다 먼저 로드
dotenv.config();
import { errorHandlerMiddleware } from './middlewares/error-handler.middleware';
import { prisma } from './utils/prisma.util';
const app = express();
const SERVER_PORT = process.env.SERVER_PORT || 3000;
// 미들웨어
app.use(cors());
app.use(express.json());
app.use(cookieParser());
// 라우트 정의
app.get('/', (req: Request, res: Response) => {
res.json({ message: 'API 서버가 실행 중입니다.' });
});
console.log('DB 연결 테스트 시작...');
prisma.$queryRaw`SELECT 1`;
// 에러 처리 미들웨어 등록
app.use(errorHandlerMiddleware);
app.listen(SERVER_PORT, () => {
console.log(`서버가 포트 ${SERVER_PORT}에서 실행 중입니다`);
});
아래 명령을 통해 프로젝트 실행합니다.
npm run dev
아래와 같은 출력이 나오면 정상적으로 실행 및 DB 연결이 된겁니다.
[nodemon] restarting due to changes...
[nodemon] starting `ts-node src/index.ts`
DB 연결 테스트 시작...
서버가 포트 3000에서 실행 중입니다
prisma:info Starting a mysql pool with 13 connections.
DB 연결에 성공했습니다.
문제:
Could not find a declaration file for module 'cookie-parser'
원인:
cookie-parser
패키지의 TypeScript 타입 정의가 없어서 발생하는 에러
해결:
npm install --save-dev @types/cookie-parser
문제:
No overload matches this call. Argument of type '(err: any, req: Request, res: Response, next: NextFunction) => Response' is not assignable to parameter of type 'PathParams'
원인:
Express는 에러 핸들러를 특별한 방식으로 인식하는데, 4개의 매개변수를 가진 함수가 에러 핸들러로 인식되지 않았습니다.
해결:
커스텀 에러 인터페이스를 정의하여 타입 안전성 확보
// backend/src/middlewares/error-handler.middleware.ts
import { Request, Response, NextFunction } from 'express';
import HttpException from '../utils/error-exception.util';
export const errorHandlerMiddleware = (
err: HttpException,
req: Request,
res: Response,
next: NextFunction
) => {
// 에러 처리 로직
};
문제:
Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext'...
원인:
비동기 함수로 래핑하여 사용 (TypeScript 설정 변경 없이 해결)
해결:
const connectDB = async () => {
try {
await prisma.$connect();
console.log('DB 연결에 성공했습니다.');
} catch (error) {
console.error('DB 연결에 실패했습니다.', error);
}
};
connectDB();