서버는 기본적으로 Express.js, Typescript으로 만듭니다.
DB는 MySQL, Sequelize ORM을 사용합니다.
테스팅 라이브러리는 Mocha, Chai, Supertest를 사용합니다.
그리고 개발 환경에서의 편리한 서버 구동을 위해 nodemon을 사용합니다.
프로젝트가 될 폴더를 만들고 (저는 nullsafety-be라고 지었습니다)
npm init -y
위 명령어로 package.json파일을 만듭니다.
그리고 express를 받아주고, TS환경에서 만들 거니까 ts-node를 받아줍니다.
npm i express ts-node
이제 타입스크립트, 테스팅 라이브러리, nodemon을 받습니다.
npm i -D typescript chai mocha supertest nodemon @types/chai @types/mocha @types/supertest @typs/node @types/express
이것저것 받았으니 package.json의 dependencies와 devDependencies에 기록이 다 되었겠죠.
이제 아래 명령어로 tsconfig.json파일을 만듭니다.
tsc --init
tsc를 찾을 수 없다는 에러가 뜨면 아래 명령어로 대체합니다.
npx tsc --init
이제 서버의 진입점, app.ts를 만듭니다.
근데 바로 띡 만들지 말고, 프로젝트 구조 관리를 위해 src폴더를 만들고 거기에 app.ts를 둡니다. 그리고 다음과 같이 작성합니다.
/src/app.ts
import { NextFunction, Request, Response } from "express";
const express = require("express");
const app = express();
// node 환경변수에 지정해놓은 포트번호가 있으면 그걸로.
// 없다면 5000번 포트로. (5000이 아니어도 됩니다)
app.set("port", process.env.PORT || 5000);
// 서버에 들어온 유효한 요청을 처리하는 router를 만들어서 끼워줍니다.
// 이제 바로 만들건데, 바로 만들거니까 만들었다고 치고 코드 먼저 작성해 줍니다.
app.use("/", require("./routes/rootRouter"));
// 클라이언트가 요청하는 경로가 위 라우터에 있는 경로가 아니라면
// router middleware를 그냥 지나서 이곳으로 오겠죠. 즉 여기는 404 Not Found를 담당하는 미들웨어입니다.
app.use((req: Request, res: Response, next: NextFunction) => {
const err: any = new Error("Not Found");
err.status = 404;
next(err);
});
// 에러 발생 시 처리하는 미들웨어
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
res.status(err.status || 500);
if (err.message === "Not Found") res.send("404 Not Found");
else res.send("Internal server error");
});
app.listen(app.get("port"), () => {
console.log(`Listen at `, app.get("port"));
});
module.exports = app;
실제 프로젝트 소스가 될 테니 좀 전에 만든 src 폴더에 만듭니다.
라우터가 위 코드에서는 하나밖에 없지만 여러개로 분할할 지도 모릅니다. 그래서 /src/routes폴더를 만들어서 여기에 라우터 파일을 만듭니다.
/src/routes/rootRouter.ts
import { Request, Response } from "express";
const router = require("express").Router();
router.get("/", (req: Request, res: Response) => {
res.send("Hello");
});
module.exports = router;
아직은 루트경로(/)에 대해 그냥 "Hello"만 응답합니다. 본격적인 기능 개발은 나중에 하고, 오늘은 서버 잘 돌아가는 지 보고 테스팅을 세팅할겁니다.
node는 typescript파일을 실행하지 못합니다.
그래서 아까 ts-node를 따로 받았습니다.
node가 아닌 ts-node에게 app.ts를 실행하라고 해야 합니다.
package.json
...
"scripts": {
"start": "ts-node ./src/app.ts",
},
...
터미널에
npm start
명령을 입력해서 서버를 실행시켜 봅니다.
잘 될 겁니다.
브라우저에서 localhost:5000 주소로 접속해 봅니다.
Hello가 뜰 겁니다.
node 혹은 ts-node로 서버를 실행하면, 개발 중 서버 소스를 수정했을 때 직접 서버를 재시동해줘야 합니다. nodemon은 소스 변경 시 자기가 알아서 재시동 해줍니다. nodemon은 아까 받았고, package.json에 nodemon으로 서버를 실행하는 script만 넣어주면 됩니다.
...
"scripts": {
"start": "ts-node ./src/app.ts",
"dev": "nodemon --exec ts-node src/app.ts"
},
...
테스트코드를 작성해서 서버를 테스트를 해봅시다.
테스트코드는 /__test__폴더를 만들어서 따로 모아두려고 합니다.
특정 기능 말고, 무슨 서버든 일단 서버라면 이건 해야지! 하는 점들이 있습니다. app.spec.ts 파일을 만들어 그런 사항들을 테스트하는 코드를 작성하겠습니다.
일단 유효한 URL로 요청 시 응답을 잘 하는지, 잘못된 요청을 했을 때 에러 응답을 똑바로 하는지 테스트합니다.
/__test__/app.spec.ts
import { expect, should } from "chai";
import { describe, it } from "mocha";
import request from "supertest";
const app = require("../src/app");
describe("[Invalid URL]", () => {
it("GET: Invalid URL", () => {
request(app)
.get("/invalid")
.expect(404)
.end((err, res) => {
expect(res.text).to.equal("404 Not Found");
});
});
it("POST: Invalid URL", () => {
request(app)
.post("/invalid")
.expect(404)
.end((err, res) => {
expect(res.text).to.equal("404 Not Found");
});
});
});
describe("[Basic App]", () => {
it("GET: /", () => {
request(app)
.get("/")
.expect(200)
.end((err, res) => {
expect(res.text).to.equal("Hello");
});
});
});
테스트 코드를 실행하는 script를 package.json에 추가합니다.
...
"scripts": {
"test": "mocha __test__/**/*.ts -r ts-node/register",
"start": "ts-node ./src/app.ts",
"dev": "nodemon --exec ts-node src/app.ts"
},
...
이제 테스트를 실행해 봅니다.
npm run test

mocha가 테스트를 잘 수행했고 겨우 통과했네요!
다음 포스트에선 공짜로 배포할 곳을 정해서, 오늘 작성한 초기상태의 서버를 배포하고 배포자동화까지 적용해 보겠습니다.