Node.js + Express + TypeScript 게시판 만들기 #3 – dotenv 설정과 Controller · Service · Middleware 구조화

Node.js + Express + TypeScript 게시판 만들기 #1 – 개발 환경 구성

Node.js + Express + TypeScript 게시판 만들기 #2 – nodemon 설정과 라우터 분리

자 지난 글에서는 nodemon 설정과 라우터 분리를 통해 기본 세팅을 이어갔었다.

이번 글에서는 다음을 진행해보려 한다.

  • dotenv 를 이용한 환경변수 관리
  • Controller / Service 계층 분리
  • 에러 처리 미들웨어 추가

1. dotenv 설치

> npm install dotenv
> npm install -D @types/dotenv

그 후 .env 파일 생성.

나중에 개발, 운영, 로컬 다 따로 나눌 수 있지만 우선은 하나로 가고 여기에 디비정보라던지 그런 환경변수들을 넣어준다.

기본적인 PORT 먼저 3000으로 설정했다.

그리고 index.ts에서 dotenv를 로드한다.

import express from 'express';
import indexRouter from './routes/index_routes';
import dotenv from 'dotenv';

dotenv.config();

const app = express();
const port = Number(process.env.PORT) || 3000;

app.use('/', indexRouter);

app.listen(port, () => {
    console.log('server start!!!');
    console.log("PORT : ", port);
});

process.env.PORT로 아까 작성했던 포트를 가져온다.

서버 시작을 해서 콘솔로 확인할 수 있다.

2. Controller 분리

기존에는 라우터에서 바로 응답을 보냈지만 이제 Controller에서 요청/응답을 담당하도록 분리한다.

src/controller/home_controller.ts 생성

import { Request, Response } from 'express';

export const home = (req: Request, res: Response) => {
    res.send('hello controller');
}

3. Router에서 Controller 연결

index_routes.ts 수정

import { Router } from 'express';
import { home } from '../controller/home_controller';

const router = Router();

router.get('/', home);

export default router;

기존에 라우터에서 바로 응답을 처리해줬던 것을 컨트롤러로 보내서 처리해준다.

http://localhost:3000 호출

잘 출력된다.

4. Service 계층 추가

controller에 로직을 다 넣으면 복잡해지므로 비즈니스 로직은 Service로 분기한다.

뭔가 자바에서 mvc패턴으로 개발했던 구조와 비슷해서 좋다.

src/service 디렉토리를 만들고 home_service.ts 를 생성한다.

참고로 index_routes, home_service 이렇게 스네이크식으로 네이밍을 짓는 이유는 나중에 프론트엔드 또한 node.js로 붙일건데 구조도 비슷하기에 네이밍에 구별을 두려고 했다.

프론트는 homeIndex 이렇게 카멜스타일로 하려고 한다.

src/service/home_service.ts

export const getHomeMessage = () => {

    return 'hello service';

}

앞서 작성한 home_controller.ts 수정한다.

import { Request, Response } from 'express';
import { getHomeMessage } from '../service/home_service';

export const home = (req: Request, res: Response) => {
    res.send(getHomeMessage());
}

컨트롤러에서 처리하던것을 서비스의 함수호출로 처리한다.

이제 Request, Response 구조는 아래처럼 진행된다.

Router -> Controller -> Service

localhost:3000 으로 확인해보면 service 까지 잘 온 것을 알 수 있다.

5. 에러 처리 미들웨어 추가

src/middleware 디렉토리 생성 error_middleware.ts 생성

import { Request, Response, NextFunction} from 'express';

export const errorHandler = (
    err: any,
    req: Request,
    res: Response,
    next: NextFunction

) => {
    console.error(err);
    res.status(500).json({ message: 'Internal Server Error'});
}

에러 처리 전용 미들웨어를 작성했다.

그리고 index.ts를 수정한다.

...
import { errorHandler } from './middleware/error_middleware';
...

...
app.use(errorHandler);
...

위 두 줄을 추가해준다.

그리고 index_routes.ts에도 테스트용 에러 라우트도 추가해준다.

router.get('/err', () => {
    throw new Error('test error');
});

방금 추가한 테스트용 라우트를 호출해본다.

localhost:3000/err 로 호출하면 화면에 응답과 콘솔에 로그를 확인할 수 있다.

6. 404(Not Found) 미들웨어 추가

존재하지 않는 경로에 대한 처리를 위해 404 미들웨어도 추가한다.

src/middleware/notfnd_middleware.ts 생성

import { Request, Response, NextFunction } from 'express';

export const notFound = (req: Request, res: Response, next: NextFunction) => {
    res.status(404).json({ message: 'Not Found'});
}

그리고 역시 index.ts 모든 라우터 뒤에 등록한다.

...
import { notFound } from './middleware/notfnd_middleware';
...

...
app.use(notFound);
...

그리고 아무 url이나 입력해서 호출해보면

잘 확인할 수 있다.

7. 현재까지 전체 구조 정리

src
 ├─ controller
 │   └─ home_controller.ts
 ├─ service
 │   └─ home_service.ts
 ├─ routes
 │   └─ index_routes.ts
 ├─ middleware
 │   ├─ error_middleware.ts
 │   └─ notfnd_middleware.ts
 ├─ index.ts

이제 기본적인 백엔드 뼈대는 어느정도 완성이고 이제 저 구조에서 필요한 기능마다 붙여가며 개발하면 된다.

다음 글에서는

  • morgan 으로 HTTP 요청 로그 관리
  • helme 으로 기본 보안 HTTP 헤더 자동 적용
  • cors 설정으로 프론트엔드와 API 서버 분리 대비

환경세팅 거의 다 온 것 같다… 이제 조금만 더..

댓글 남기기