Node Express + TypeScript 세팅
포스트
취소

Node Express + TypeScript 세팅

본 포스트는 NodeExpressTypeScript를 이용한 백엔드 구축 세팅에 대한 포스팅입니다.

📃 설치

0️⃣ 필수로 설치할 패키지

1
2
npm i express
npm i -D typescript nodemon @types/node @types/express concurrently
  1. typescript: 타입스크립트를 사용하기 위해 설치
  2. nodemon: 코드 변경에 의한 서버 자동 재시작을 위해 설치
  3. @types/node: Node.js에 타입을 적용하기 위해 설치
  4. @types/express: Express에 타입을 적용하기 위해 설치
  5. concurrently: 여러 명령어를 동시 실행하기 위해 설치

1️⃣ 추가로 설치하면 좋은 패키지

1
2
3
4
5
6
7
8
9
10
11
# ".env" 파일 즉 환경 변수를 사용하기 위해 설치
npm i dotenv

# "cors" 문제를 쉽게 해결하기 위해 설치
npm i cors
npm i -D @types/cors

# API 요청을 위해 설치
npm i axios

# 이후에 더 설치하면 추가할 예정...

⚙️ 세팅

0️⃣ TypeScript 세팅

1
2
# typescript를 설치해야 가능 ( 혹은 글로벌로 설치했다면 가능 )
npx tsc --init

명령어를 실행하면 tsconfig.json이 생성됩니다.
해당 파일을 TypeScript을 세팅하는 파일이므로 원하는 부분이 있다면 수정하시면 됩니다.

1️⃣ 기본 세팅

1. app.js ( 진입점 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ##### app.ts #####

// // dotenv를 설치했다면
// import * as dotenv from "dotenv";
// dotenv.config();

import express, { Request, Response } from "express";
// // cors 설치했다면
// import cors from "cors";

// // 각 router
// import movieRouter from "./routes/movie";
// import dramaRouter from "./routes/drama";
// import bookRouter from "./routes/book";

// 모든 에러를 처리하는 핸들러
import { errorHandler } from "./handler";

const app = express();
app.set("port", 3050);

// 2022/12/22 추가 -> body parser ( 이거 안넣으면 POST 요청 시 보내는 데이터가 파싱되지 않아서 빈 값만 출력됩니다... )
// 까먹고 안넣었다가 axios.post()가 왜 데이터를 못보내지? 왜 못받지? 이러고 혼자 한참 헤맸습니다...
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// // "cors"를 설치했다면 ( 개발모드에서는 "http://localhost:3000"에서의 송수신을 허락한다는 의미 )
// const corsOrigin =
//   process.env.NODE_ENV === "development" ? ["http://localhost:3000"] : [""];
// app.use(cors({ credentials: true, origin: corsOrigin }));

// 기본으로 서버 URL에 들어왔을 때 "index.html" 보여주기 ( 이후에 API문서처럼 만들면 좋을 것 같음 )
app.get("/", (req: Request, res: Response) => {
  res.sendFile(__dirname + "/index.html");
});

// // router 연결
// app.use("/api/movie", movieRouter);
// app.use("/api/drama", dramaRouter);
// app.use("/api/book", bookRouter);

// error 처리 핸들러(미들웨어)
app.use(errorHandler);

// express 실행
app.listen(app.get("port"), () => {
  console.log(`${app.get("port")}번 실행중...`);
});

2. type.ts ( 공통으로 사용할 타입을 정의 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ##### type.ts #####

type ApiType = "axios" | "prisma" | "unknown";

/**
 * "Response" 필수 타입을 지정한 타입
 * 모든 응답에서 아래와 같은 규약에 맞게 응답하기위해 사용하는 타입
 */
export type ApiResponse<T> = {
  meta: { ok: boolean; type?: ApiType };
  data: { message: string } & T;
};
/**
 * 에러를 위한 "Response"
 */
export type ApiErrorResponse = ApiResponse<{}>;

3. handler.ts ( 에러 처리 핸들러(미들웨어) )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ##### handler.ts #####

// type
import { AxiosError } from "axios";
import type { ErrorRequestHandler, Response } from "express";
import type { ApiErrorResponse } from "../types";

/**
 * 에러 처리 핸들러
 * 에러 발생 시 해당 핸들러(미들웨어)로 에러가 들어옴
 * 
 * ex) 특정 라우터에서 "next(error)"을 사용하면 해당 핸들러의 첫 번째 인자로 error가 그대로 들어옴
 * 따라서 해당 error에 맞게 적절한 응답을 해주면 됨
 * 
 * @param error 에러 객체
 * @param req "express"의 request
 * @param res "express"의 response
 * @param next "express"의 next
 */
export const errorHandler: ErrorRequestHandler = (
  error,
  req,
  res: Response<ApiErrorResponse>,
  next
) => {
  if (error instanceof AxiosError) {
    console.error("axios 에러 >> ", error);

    res.status(500).json({
      meta: { ok: false, type: "axios" },
      data: { message: "API측 문제입니다.\n잠시후에 다시 시도해주세요!" },
    });
  } else {
    console.error("알 수 없는 에러 >> ", error);

    res.status(500).json({
      meta: { ok: false, type: "unknown" },
      data: { message: "서버의 문제입니다.\n잠시후에 다시 시도해주세요!" },
    });
  }
};

4. router.ts ( router 작성 예시 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// ##### router 예시 #####

import express from "express";
import axios from "axios";

// type
import type { Request, Response, NextFunction } from "express";
import type { ApiResponse } from "../types";

const router = express.Router();

router.get(
  "/",
  async (
    req: Request<{ a: string }, { b: string }, { c: string }, { d: string }>,
    res: Response<ApiResponse<{ name: string }>>,
    next: NextFunction
  ) => {
    try {
      // const { a } = req.params;
      // req.res?.json({ b: "이것은 뭔지 모르겠음" });
      // const { c } = req.body;
      // const { d } = req.query;

      const { data } = await axios.get<{name: string}>("https://jsonplaceholder.typicode.com/users/1");

      return res.status(200).json({
        meta: { ok: true },
        data: { message: `특정 이름을 찾았습니다.`, name: data.name },
      });
    } catch (error) {
      // 에러를 넘겨주면 이전에 봤던 "errorHandler"로 넘어가서 처리됨
      next(error);
    }
  }
);

export default router;

5. environment.d.ts ( 환경변수 타입 적용 )

1
2
3
4
5
6
7
8
9
10
// ##### @types/environment.d.ts #####

namespace NodeJS {
  interface ProcessEnv extends NodeJS.ProcessEnv {
    NODE_ENV: "development" | "production";

    KAKAO_API_URL: string;
    KAKAO_API_KEY: string;
  }
}

위와 같이 파일을 생성하기만 하면 process.env.KAKAO_API_URL의 자동 완성과 타입을 지원해줍니다.

2️⃣ package.json 세팅

1
2
3
4
5
6
7
{
  "scripts": {
    "build": "npx tsc",
    "start": "node ./src/app.js",
    "dev": "concurrently \"npx tsc --watch\" \"nodemon ./src/app.js\""
  },
}

concurrently를 이용해서 두 개의 명령어를 동시에 실행합니다.

  1. npx tsc --watch를 이용해서 코드가 변화를 감지하여 ts -> js로 바꿔줍니다.
  2. nodemon ./src/app.js을 이용해서 코드가 변화하면 서버가 자동으로 재시작하게 해줍니다.

./src/app.js는 현재 진입점 파일이 존재하는 경로로 작성하면 됩니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.