부트캠프 미니프로젝트2 구움(Gooum)의 백엔드를 Python(FastAPI)에서 Node.js + TypeScript로 전환하고, Azure App Service에 GitHub Actions로 자동 배포까지 연결했다.
핵심 기능이 실시간 채팅, 동시 문서 편집이라 다수 클라이언트가 동시 접속해서 이벤트를 계속 주고받는 구조인데, Node.js의 이벤트 루프 기반 비동기 I/O가 이런 워크로드에 잘 맞고 Socket.io, Yjs 등 관련 라이브러리 생태계도 npm 쪽이 두터워서 스택을 바꾸기로 결정했다.
npm init -y
# 개발용 도구
npm install -D typescript tsx @types/node @types/express
# 운영용 라이브러리
npm install express dotenv
Node.js는 패키지를 프로젝트 폴더 안 node_modules에 설치하기 때문에 폴더 자체가 이미 격리된 환경이다. 파이썬의 venv 같은 가상환경 개념이 따로 필요 없다.
npm install할 때 -D(--save-dev) 옵션을 붙이면 devDependencies(개발용)로, 안 붙이면 dependencies(운영용)로 package.json에 기록된다.
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
TypeScript 컴파일러(tsc)가 .ts 파일을 어떻게 .js로 변환할지 정하는 설정 파일이다.
| 옵션 | 의미 |
|---|---|
target | 컴파일된 JS가 어떤 자바스크립트 버전 문법을 쓸지 (ES2022) |
module | 모듈 시스템 방식. CommonJS로 지정해 배포/라이브러리 호환성을 우선함 |
rootDir | 컴파일 대상 소스 파일들이 있는 루트 폴더 (src/) |
outDir | 컴파일 결과물(.js)이 출력될 폴더 (dist/) |
esModuleInterop | import express from 'express' 같은 ES Module 스타일 import 문법을 CommonJS 라이브러리에서도 에러 없이 쓸 수 있게 해줌 |
forceConsistentCasingInFileNames | 파일명 대소문자를 엄격하게 체크 (OS별로 대소문자 구분이 달라 생기는 오류 방지) |
strict | 타입 검사를 엄격하게 적용 (null 체크, 암시적 any 금지 등) |
skipLibCheck | node_modules 안 라이브러리의 타입 정의 파일까지는 검사하지 않아 컴파일 속도를 높임 |
include | 컴파일 대상 파일 범위 지정 (src/ 하위 전체) |
{
"name": "gooum-be",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "tsx src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ureca-Gooum/Gooum-BE.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"bugs": {
"url": "https://github.com/ureca-Gooum/Gooum-BE/issues"
},
"homepage": "https://github.com/ureca-Gooum/Gooum-BE#readme",
"devDependencies": {
"@types/express": "^5.0.6",
"@types/node": "^26.1.1",
"ts-node": "^10.9.2",
"tsx": "^4.23.1",
"typescript": "^7.0.2"
},
"dependencies": {
"dotenv": "^17.4.2",
"express": "^5.2.1"
}
}
npm run dev : 로컬 개발 중 TS 파일 바로 실행 (tsx 사용, 별도 설정 없이 esbuild 기반으로 즉시 실행됨)npm run build : TS → JS 컴파일 (dist/ 생성)npm start : 컴파일된 JS로 실제 서버 구동 (배포 환경에서 사용)"type": "commonjs" : 모듈 시스템을 CommonJS로 명시. tsx가 import 문법을 알아서 처리해주고, tsc 빌드나 일부 라이브러리와의 호환성을 위해 module(ESM)이 아닌 commonjs로 고정ts-node는 devDependencies에 남아있지만 실제 실행은 tsx로 하고 있어 사실상 미사용 상태 (정리 대상)import express, { Request, Response } from "express";
import dotenv from "dotenv";
dotenv.config();
const app = express();
// Azure가 임의로 부여하는 포트(process.env.PORT)를 우선적으로 사용하도록 설정!
const PORT = process.env.PORT || 8000;
app.use(express.json());
// 배포 테스트용 핑 API
app.get("/ping", (req: Request, res: Response) => {
res.status(200).json({
message: "pong! Node.js TS server is running on Azure!",
});
});
app.listen(PORT, () => {
console.log(`🚀 Server is running on port ${PORT}`);
});
dotenv.config()는 다른 코드가 process.env를 사용하기 전에 가장 먼저 실행되어야 하므로 파일 최상단에 둔다.process.env.PORT || 8000 : 로컬에서는 8000번을 쓰고, 배포 환경(Azure)에서는 플랫폼이 지정한 포트를 우선 사용하도록 유연하게 작성.# 환경 변수 및 비밀값
.env
.env.local
# 의존성 패키지 폴더
node_modules/
# 빌드 결과물
dist/
out/
build/
# 개발 도구 및 IDE 설정
.vscode/
.idea/
# OS 및 임시 파일
.DS_Store
Thumbs.db
npm-debug.log*

기존에 다른 스택(Python 등)으로 쓰던 App Service가 있다면, 설정만 바꾸는 것보다 새로 생성하는 편이 안전하다. 내부 빌드 엔진이나 환경변수가 이전 스택 기준으로 남아 있어 오류가 나기 쉽다.
main) 선택 후 저장
저장하면 Azure가 자동으로 .github/workflows/*.yml 워크플로 파일을 레포지토리에 생성해준다. 이후 해당 브랜치에 push할 때마다 아래 과정이 자동으로 실행된다.

npm install → npm run buildpackage.json에 build 스크립트가 정확히 정의돼 있어야 3번 단계가 정상 동작한다.
Azure Portal → App Service → 구성(Configuration) → 일반 설정(General settings) → 시작 명령

포트는 명령어에 별도로 적을 필요 없다. 코드에서 process.env.PORT를 리슨하도록 작성했다면 Azure가 내부적으로 지정한 포트와 자동으로 맞물린다.
Azure Linux App Service는 기본적으로 내부 포트를 8080으로 사용한다. 코드에서 process.env.PORT || 8000으로 작성해뒀다면 별도 설정 없이 자동으로 맞춰지지만, 만약 배포 후 502 Bad Gateway가 발생한다면 아래처럼 명시적으로 지정한다.
WEBSITES_PORT8080)https://<앱이름>.azurewebsites.net/ping
{ "message": "pong! Node.js TS server is running!" }

Log stream에서 아래 메시지가 뜨면 정상 기동 상태다.
🚀 Server is running on port 8080

tsx로 TS 실행 환경 구성, dotenv로 비밀값 분리, .gitignore로 민감 정보 제외node dist/server.js, 포트는 코드에서 process.env.PORT로 동적으로 대응WEBSITES_PORT 환경변수로 포트 명시