create-express-esm 1.1.3 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -171
- package/bin/cli.js +109 -99
- package/package.json +40 -40
- package/template/js/_env +2 -0
- package/template/js/_gitignore +18 -0
- package/template/{package.json → js/package.json} +18 -18
- package/template/{src → js/src}/app.js +18 -18
- package/template/{src → js/src}/controllers/userController.js +9 -9
- package/template/{src → js/src}/routes/userRoutes.js +7 -7
- package/template/{src → js/src}/server.js +8 -8
- package/template/{src → js/src}/services/userService.js +7 -7
- package/template/ts/_env +0 -0
- package/template/ts/_gitignore +0 -0
- package/template/ts/package.json +23 -0
- package/template/ts/src/app.ts +20 -0
- package/template/ts/src/controllers/userController.ts +17 -0
- package/template/ts/src/routes/userRoutes.ts +8 -0
- package/template/ts/src/server.ts +8 -0
- package/template/ts/src/services/userService.ts +14 -0
- package/template/ts/tsconfig.json +15 -0
- package/template/_env +0 -1
- package/template/_gitignore +0 -4
- /package/template/{.env.example → js/.env.example} +0 -0
package/README.md
CHANGED
|
@@ -1,171 +1,171 @@
|
|
|
1
|
-
# 🚀 Create Express ESM (CLI)
|
|
2
|
-
|
|
3
|
-
> **Modern Express Generator**
|
|
4
|
-
>
|
|
5
|
-
> "1초 만에 완성하는 Modern Express(ESM) 환경"
|
|
6
|
-
>
|
|
7
|
-
> CommonJS(require)가 아닌, 최신 ES Modules(import/export) 문법을 기반으로 하는 Express 프로젝트 구조를 자동으로 생성해주는 CLI 도구입니다.
|
|
8
|
-
|
|
9
|
-
[](https://www.npmjs.com/package/create-express-esm)
|
|
10
|
-
[](https://opensource.org/licenses/MIT)
|
|
11
|
-
|
|
12
|
-
## ✨ Demo
|
|
13
|
-
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
## ✨ Key Features (핵심 기능)
|
|
17
|
-
|
|
18
|
-
기존 `express-generator`의 한계를 분석하고 개선하여 개발했습니다.
|
|
19
|
-
|
|
20
|
-
- **⚡️ 100% ES Modules**: 구식 CommonJS(`require`)를 버리고 최신 `import/export` 문법을 기본으로 채택했습니다.
|
|
21
|
-
- **🏗 Layered Architecture**: 실무 표준인 `Controller` - `Service` - `Model` 구조를 자동으로 잡아줍니다.
|
|
22
|
-
- **📦 Auto Installation**: 프로젝트 생성 후 귀찮은 `npm install` 과정을 자동으로 수행합니다.
|
|
23
|
-
- **🛠 Ready-to-Use**: `dotenv`, `cors`, `morgan`, `nodemon` 등 필수 개발 환경이 세팅되어 있습니다.
|
|
24
|
-
|
|
25
|
-
## 🚀 Quick Start (사용법)
|
|
26
|
-
|
|
27
|
-
별도의 설치 없이 `npx` 명령어로 즉시 실행할 수 있습니다.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npx create-express-esm
|
|
31
|
-
npm create express-esm
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
또는 전역으로 설치하여 사용할 수도 있습니다
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
npm install -g create-express-esm
|
|
38
|
-
create-express-esm
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## 📂 Project Structure (폴더 구조)
|
|
42
|
-
|
|
43
|
-
이 도구는 **Layered Architecture (계층형 아키텍처)**를 기반으로 프로젝트를 생성합니다.
|
|
44
|
-
**관심사 분리(Separation of Concerns)** 원칙을 적용하여, 로직이 섞이지 않고 유지보수가 쉬운 구조를 제공합니다.
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
my-app/
|
|
48
|
-
├── src/
|
|
49
|
-
│ ├── config/ # ⚙️ 환경변수 및 DB 연결 설정
|
|
50
|
-
│ ├── controllers/ # 🕹️ 요청과 응답 처리 (Controller Layer)
|
|
51
|
-
│ ├── models/ # 🗄️ 데이터베이스 스키마 (Data Access Layer)
|
|
52
|
-
│ ├── routes/ # 🚦 API 라우팅 정의 (Route Definitions)
|
|
53
|
-
│ ├── services/ # 🧠 비즈니스 로직 (Service Layer) - 핵심 로직!
|
|
54
|
-
│ ├── app.js # 🏗️ Express App 설정 (Middleware, CORS 등)
|
|
55
|
-
│ └── server.js # 🚀 서버 실행 진입점 (Entry Point)
|
|
56
|
-
├── .env # 🔐 환경 변수 (Port, DB Key 등)
|
|
57
|
-
├── .gitignore # 🙈 Git 무시 설정
|
|
58
|
-
└── package.json # 📦 프로젝트 의존성 및 스크립트
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## 🛠 Tech Stack (기술 스택)
|
|
62
|
-
|
|
63
|
-
- **Runtime**: Node.js
|
|
64
|
-
- **Framework**: Express.js
|
|
65
|
-
- **Architecture**: Layered Pattern (Controller-Service-Model)
|
|
66
|
-
- **Language**: JavaScript (ES6+ Modules)
|
|
67
|
-
- **Tools**:
|
|
68
|
-
- `dotenv` (환경변수 관리)
|
|
69
|
-
- `cors` (Cross-Origin 리소스 공유 설정)
|
|
70
|
-
- `morgan` (HTTP 요청 로그 기록)
|
|
71
|
-
- `nodemon` (개발 생산성 향상/자동 재시작)
|
|
72
|
-
|
|
73
|
-
## 🛠️ Engineering Deep Dive
|
|
74
|
-
|
|
75
|
-
단순한 도구 개발을 넘어, Node.js의 런타임 환경과 모듈 시스템의 차이를 깊이 있게 이해하기 위해 진행한 프로젝트입니다.
|
|
76
|
-
|
|
77
|
-
### - Project Motivation (Why)
|
|
78
|
-
|
|
79
|
-
대부분의 Express 예제나 스캐폴딩 도구들이 여전히 CommonJS(require) 방식을 사용하고 있습니다. 하지만 최신 Node.js 생태계는 ES Modules(import)로 전환되고 있습니다.
|
|
80
|
-
**"최신 문법을 지원하는 환경을 매번 수동으로 설정하는 비효율을 해결하자"**는 목표로 시작했으며, 프레임워크 없이 순수 Node.js의 `fs`, `path` 모듈을 다루며 CLI 도구의 동작 원리를 체득하고자 했습니다.
|
|
81
|
-
|
|
82
|
-
### - Architecture
|
|
83
|
-
|
|
84
|
-
1. **비동기 파일 시스템 제어**: 대량의 템플릿 파일을 생성할 때 I/O 블로킹을 막기 위해 `fs.promises`와 `async/await`를 사용하여 안정적인 파일 쓰기 작업을 구현했습니다.
|
|
85
|
-
2. **동적 템플릿 생성**: 사용자가 입력한 프로젝트 이름을 `package.json` 등에 동적으로 주입하여, 생성 즉시 실행 가능한 상태를 보장합니다.
|
|
86
|
-
|
|
87
|
-
## 🔥 Challenges & Troubleshooting
|
|
88
|
-
|
|
89
|
-
개발 과정에서 마주친 기술적 난관과 해결 과정입니다.
|
|
90
|
-
|
|
91
|
-
### 1. ESM 환경에서의 경로 시스템 구축 (Transition to ESM)
|
|
92
|
-
|
|
93
|
-
> **🔴 Problem:** `ReferenceError: __dirname is not defined`
|
|
94
|
-
|
|
95
|
-
프로젝트를 `type: "module"`(ESM)로 설정한 후, 템플릿 폴더 경로를 참조하기 위해 관습적으로 `__dirname`을 사용했으나 에러가 발생하며 앱이 종료되었습니다.
|
|
96
|
-
|
|
97
|
-
**🔍 Root Cause & Solution**
|
|
98
|
-
|
|
99
|
-
Node.js의 CommonJS 환경에서는 `__dirname`이 기본적으로 주입되지만, ESM 표준 스펙에는 이 변수가 존재하지 않습니다. 이를 해결하기 위해 `url`과 `path` 모듈을 조합하여 Polyfill(직접 구현) 했습니다.
|
|
100
|
-
|
|
101
|
-
```javascript
|
|
102
|
-
import path from "path";
|
|
103
|
-
import { fileURLToPath } from "url";
|
|
104
|
-
|
|
105
|
-
// 1. 현재 파일의 URL(file://...)을 시스템 경로로 변환
|
|
106
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
107
|
-
|
|
108
|
-
// 2. 파일 경로에서 디렉토리 경로만 추출하여 __dirname 구현
|
|
109
|
-
const __dirname = path.dirname(__filename);
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 2. CLI의 실행 위치와 파일 위치의 혼동 (Execution Context)
|
|
113
|
-
|
|
114
|
-
> **🔴 Problem:** `ENOENT: no such file or directory`
|
|
115
|
-
|
|
116
|
-
로컬 테스트(`node bin/cli.js`) 시에는 정상 작동했으나, `npm link` 후 다른 경로(바탕화면 등)에서 실행했을 때 템플릿 폴더를 찾지 못하는 문제가 발생했습니다.
|
|
117
|
-
|
|
118
|
-
**🔍 Root Cause**
|
|
119
|
-
|
|
120
|
-
CLI 도구 개발 시 **'코드의 위치(Source)'**와 **'명령어 실행 위치(Target)'**가 다를 수 있음을 간과했습니다. 템플릿을 찾을 때 `process.cwd()`(사용자 위치)를 기준으로 탐색했기 때문에 발생한 문제였습니다.
|
|
121
|
-
|
|
122
|
-
**✅ Solution**
|
|
123
|
-
|
|
124
|
-
리소스의 성격에 따라 기준 경로를 명확히 분리하여 해결했습니다.
|
|
125
|
-
|
|
126
|
-
- **Source (Template)**: 코드가 설치된 곳에 항상 함께 존재하므로 `__dirname` 기준.
|
|
127
|
-
- **Target (Project)**: 사용자가 명령어를 실행한 위치에 생성되어야 하므로 `process.cwd()` 기준.
|
|
128
|
-
|
|
129
|
-
```javascript
|
|
130
|
-
// [Source] 템플릿 경로: 내 코드가 설치된 곳 기준
|
|
131
|
-
const templateDir = path.join(__dirname, "../template");
|
|
132
|
-
|
|
133
|
-
// [Target] 생성 경로: 사용자가 명령어를 실행한 곳 기준
|
|
134
|
-
const targetDir = path.join(process.cwd(), projectName);
|
|
135
|
-
|
|
136
|
-
// Copy: Source -> Target
|
|
137
|
-
await copyDir(templateDir, targetDir);
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### 3. 배포 파이프라인 최적화 (Appropriate Technology)
|
|
141
|
-
|
|
142
|
-
> **🔴 Problem**
|
|
143
|
-
|
|
144
|
-
잦은 업데이트 과정에서 `버전 수정` -> `태그 생성` -> `깃 푸시` -> `npm 배포`라는 4단계 프로세스를 수동으로 반복하다 보니, 순서를 누락하거나 버전을 잘못 기입하는 휴먼 에러가 발생했습니다.
|
|
145
|
-
|
|
146
|
-
**🔍 Approach & Solution**
|
|
147
|
-
|
|
148
|
-
GitHub Actions와 같은 CI/CD 도구 도입을 고려했으나, 1인 개발 프로젝트 규모에 비해 설정 비용이 크고 오버엔지니어링이라는 판단이 들었습니다. 대신 Node.js의 내장 기능인 **NPM Scripts**를 활용하는 것이 가장 효율적인 **'적정 기술(Appropriate Technology)'**이라 판단했습니다.
|
|
149
|
-
|
|
150
|
-
```json
|
|
151
|
-
// package.json
|
|
152
|
-
"scripts": {
|
|
153
|
-
// patch 버전 업 -> 깃 태그 푸시 -> npm 배포를 명령어 한 줄로 원자적(Atomic) 실행
|
|
154
|
-
"deploy": "npm version patch && git push origin main --tags && npm publish"
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## 📝 Retrospective
|
|
159
|
-
|
|
160
|
-
- **Legacy to Modern**: Node.js 생태계가 CJS에서 ESM으로 전환되는 과도기에서 발생하는 호환성 문제를 직접 겪고 해결하며, 모던 자바스크립트 환경에 대한 이해도를 높였습니다.
|
|
161
|
-
- **Consumer to Producer**: 항상 라이브러리를 사용하기만 하던 입장에서, 직접 도구를 만들어 배포하는 생산자가 되어봄으로써 오픈소스 생태계의 순환 구조를 체감했습니다.
|
|
162
|
-
- **JavaScript to TypeScript (Next Step)**: 순수 ESM 환경을 구축하며 모듈 시스템은 표준화했으나, 컴파일 단계에서 오류를 잡을 수 없는 동적 타입 언어의 한계를 체감했습니다. 프로젝트의 안정성과 유지보수성을 높이기 위해 **TypeScript** 도입과 엄격한 타입 시스템의 필요성을 깨달았으며, 차기 버전에서는 이를 지원할 계획입니다.
|
|
163
|
-
|
|
164
|
-
## 🗺️ Roadmap (Future Plans)
|
|
165
|
-
|
|
166
|
-
- [ ] **TypeScript Support**: `tsconfig.json` 자동 설정 및 `.ts` 템플릿 지원
|
|
167
|
-
- [ ] **Test Environment**: Jest/Supertest 설정 자동화
|
|
168
|
-
|
|
169
|
-
## 📝 License
|
|
170
|
-
|
|
171
|
-
This project is MIT licensed.
|
|
1
|
+
# 🚀 Create Express ESM (CLI)
|
|
2
|
+
|
|
3
|
+
> **Modern Express Generator**
|
|
4
|
+
>
|
|
5
|
+
> "1초 만에 완성하는 Modern Express(ESM) 환경"
|
|
6
|
+
>
|
|
7
|
+
> CommonJS(require)가 아닌, 최신 ES Modules(import/export) 문법을 기반으로 하는 Express 프로젝트 구조를 자동으로 생성해주는 CLI 도구입니다.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/create-express-esm)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
|
|
12
|
+
## ✨ Demo
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## ✨ Key Features (핵심 기능)
|
|
17
|
+
|
|
18
|
+
기존 `express-generator`의 한계를 분석하고 개선하여 개발했습니다.
|
|
19
|
+
|
|
20
|
+
- **⚡️ 100% ES Modules**: 구식 CommonJS(`require`)를 버리고 최신 `import/export` 문법을 기본으로 채택했습니다.
|
|
21
|
+
- **🏗 Layered Architecture**: 실무 표준인 `Controller` - `Service` - `Model` 구조를 자동으로 잡아줍니다.
|
|
22
|
+
- **📦 Auto Installation**: 프로젝트 생성 후 귀찮은 `npm install` 과정을 자동으로 수행합니다.
|
|
23
|
+
- **🛠 Ready-to-Use**: `dotenv`, `cors`, `morgan`, `nodemon` 등 필수 개발 환경이 세팅되어 있습니다.
|
|
24
|
+
|
|
25
|
+
## 🚀 Quick Start (사용법)
|
|
26
|
+
|
|
27
|
+
별도의 설치 없이 `npx` 명령어로 즉시 실행할 수 있습니다.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx create-express-esm
|
|
31
|
+
npm create express-esm
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
또는 전역으로 설치하여 사용할 수도 있습니다
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
npm install -g create-express-esm
|
|
38
|
+
create-express-esm
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 📂 Project Structure (폴더 구조)
|
|
42
|
+
|
|
43
|
+
이 도구는 **Layered Architecture (계층형 아키텍처)**를 기반으로 프로젝트를 생성합니다.
|
|
44
|
+
**관심사 분리(Separation of Concerns)** 원칙을 적용하여, 로직이 섞이지 않고 유지보수가 쉬운 구조를 제공합니다.
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
my-app/
|
|
48
|
+
├── src/
|
|
49
|
+
│ ├── config/ # ⚙️ 환경변수 및 DB 연결 설정
|
|
50
|
+
│ ├── controllers/ # 🕹️ 요청과 응답 처리 (Controller Layer)
|
|
51
|
+
│ ├── models/ # 🗄️ 데이터베이스 스키마 (Data Access Layer)
|
|
52
|
+
│ ├── routes/ # 🚦 API 라우팅 정의 (Route Definitions)
|
|
53
|
+
│ ├── services/ # 🧠 비즈니스 로직 (Service Layer) - 핵심 로직!
|
|
54
|
+
│ ├── app.js # 🏗️ Express App 설정 (Middleware, CORS 등)
|
|
55
|
+
│ └── server.js # 🚀 서버 실행 진입점 (Entry Point)
|
|
56
|
+
├── .env # 🔐 환경 변수 (Port, DB Key 등)
|
|
57
|
+
├── .gitignore # 🙈 Git 무시 설정
|
|
58
|
+
└── package.json # 📦 프로젝트 의존성 및 스크립트
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🛠 Tech Stack (기술 스택)
|
|
62
|
+
|
|
63
|
+
- **Runtime**: Node.js
|
|
64
|
+
- **Framework**: Express.js
|
|
65
|
+
- **Architecture**: Layered Pattern (Controller-Service-Model)
|
|
66
|
+
- **Language**: JavaScript (ES6+ Modules)
|
|
67
|
+
- **Tools**:
|
|
68
|
+
- `dotenv` (환경변수 관리)
|
|
69
|
+
- `cors` (Cross-Origin 리소스 공유 설정)
|
|
70
|
+
- `morgan` (HTTP 요청 로그 기록)
|
|
71
|
+
- `nodemon` (개발 생산성 향상/자동 재시작)
|
|
72
|
+
|
|
73
|
+
## 🛠️ Engineering Deep Dive
|
|
74
|
+
|
|
75
|
+
단순한 도구 개발을 넘어, Node.js의 런타임 환경과 모듈 시스템의 차이를 깊이 있게 이해하기 위해 진행한 프로젝트입니다.
|
|
76
|
+
|
|
77
|
+
### - Project Motivation (Why)
|
|
78
|
+
|
|
79
|
+
대부분의 Express 예제나 스캐폴딩 도구들이 여전히 CommonJS(require) 방식을 사용하고 있습니다. 하지만 최신 Node.js 생태계는 ES Modules(import)로 전환되고 있습니다.
|
|
80
|
+
**"최신 문법을 지원하는 환경을 매번 수동으로 설정하는 비효율을 해결하자"**는 목표로 시작했으며, 프레임워크 없이 순수 Node.js의 `fs`, `path` 모듈을 다루며 CLI 도구의 동작 원리를 체득하고자 했습니다.
|
|
81
|
+
|
|
82
|
+
### - Architecture
|
|
83
|
+
|
|
84
|
+
1. **비동기 파일 시스템 제어**: 대량의 템플릿 파일을 생성할 때 I/O 블로킹을 막기 위해 `fs.promises`와 `async/await`를 사용하여 안정적인 파일 쓰기 작업을 구현했습니다.
|
|
85
|
+
2. **동적 템플릿 생성**: 사용자가 입력한 프로젝트 이름을 `package.json` 등에 동적으로 주입하여, 생성 즉시 실행 가능한 상태를 보장합니다.
|
|
86
|
+
|
|
87
|
+
## 🔥 Challenges & Troubleshooting
|
|
88
|
+
|
|
89
|
+
개발 과정에서 마주친 기술적 난관과 해결 과정입니다.
|
|
90
|
+
|
|
91
|
+
### 1. ESM 환경에서의 경로 시스템 구축 (Transition to ESM)
|
|
92
|
+
|
|
93
|
+
> **🔴 Problem:** `ReferenceError: __dirname is not defined`
|
|
94
|
+
|
|
95
|
+
프로젝트를 `type: "module"`(ESM)로 설정한 후, 템플릿 폴더 경로를 참조하기 위해 관습적으로 `__dirname`을 사용했으나 에러가 발생하며 앱이 종료되었습니다.
|
|
96
|
+
|
|
97
|
+
**🔍 Root Cause & Solution**
|
|
98
|
+
|
|
99
|
+
Node.js의 CommonJS 환경에서는 `__dirname`이 기본적으로 주입되지만, ESM 표준 스펙에는 이 변수가 존재하지 않습니다. 이를 해결하기 위해 `url`과 `path` 모듈을 조합하여 Polyfill(직접 구현) 했습니다.
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
import path from "path";
|
|
103
|
+
import { fileURLToPath } from "url";
|
|
104
|
+
|
|
105
|
+
// 1. 현재 파일의 URL(file://...)을 시스템 경로로 변환
|
|
106
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
107
|
+
|
|
108
|
+
// 2. 파일 경로에서 디렉토리 경로만 추출하여 __dirname 구현
|
|
109
|
+
const __dirname = path.dirname(__filename);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 2. CLI의 실행 위치와 파일 위치의 혼동 (Execution Context)
|
|
113
|
+
|
|
114
|
+
> **🔴 Problem:** `ENOENT: no such file or directory`
|
|
115
|
+
|
|
116
|
+
로컬 테스트(`node bin/cli.js`) 시에는 정상 작동했으나, `npm link` 후 다른 경로(바탕화면 등)에서 실행했을 때 템플릿 폴더를 찾지 못하는 문제가 발생했습니다.
|
|
117
|
+
|
|
118
|
+
**🔍 Root Cause**
|
|
119
|
+
|
|
120
|
+
CLI 도구 개발 시 **'코드의 위치(Source)'**와 **'명령어 실행 위치(Target)'**가 다를 수 있음을 간과했습니다. 템플릿을 찾을 때 `process.cwd()`(사용자 위치)를 기준으로 탐색했기 때문에 발생한 문제였습니다.
|
|
121
|
+
|
|
122
|
+
**✅ Solution**
|
|
123
|
+
|
|
124
|
+
리소스의 성격에 따라 기준 경로를 명확히 분리하여 해결했습니다.
|
|
125
|
+
|
|
126
|
+
- **Source (Template)**: 코드가 설치된 곳에 항상 함께 존재하므로 `__dirname` 기준.
|
|
127
|
+
- **Target (Project)**: 사용자가 명령어를 실행한 위치에 생성되어야 하므로 `process.cwd()` 기준.
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// [Source] 템플릿 경로: 내 코드가 설치된 곳 기준
|
|
131
|
+
const templateDir = path.join(__dirname, "../template");
|
|
132
|
+
|
|
133
|
+
// [Target] 생성 경로: 사용자가 명령어를 실행한 곳 기준
|
|
134
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
135
|
+
|
|
136
|
+
// Copy: Source -> Target
|
|
137
|
+
await copyDir(templateDir, targetDir);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 3. 배포 파이프라인 최적화 (Appropriate Technology)
|
|
141
|
+
|
|
142
|
+
> **🔴 Problem**
|
|
143
|
+
|
|
144
|
+
잦은 업데이트 과정에서 `버전 수정` -> `태그 생성` -> `깃 푸시` -> `npm 배포`라는 4단계 프로세스를 수동으로 반복하다 보니, 순서를 누락하거나 버전을 잘못 기입하는 휴먼 에러가 발생했습니다.
|
|
145
|
+
|
|
146
|
+
**🔍 Approach & Solution**
|
|
147
|
+
|
|
148
|
+
GitHub Actions와 같은 CI/CD 도구 도입을 고려했으나, 1인 개발 프로젝트 규모에 비해 설정 비용이 크고 오버엔지니어링이라는 판단이 들었습니다. 대신 Node.js의 내장 기능인 **NPM Scripts**를 활용하는 것이 가장 효율적인 **'적정 기술(Appropriate Technology)'**이라 판단했습니다.
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
// package.json
|
|
152
|
+
"scripts": {
|
|
153
|
+
// patch 버전 업 -> 깃 태그 푸시 -> npm 배포를 명령어 한 줄로 원자적(Atomic) 실행
|
|
154
|
+
"deploy": "npm version patch && git push origin main --tags && npm publish"
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 📝 Retrospective
|
|
159
|
+
|
|
160
|
+
- **Legacy to Modern**: Node.js 생태계가 CJS에서 ESM으로 전환되는 과도기에서 발생하는 호환성 문제를 직접 겪고 해결하며, 모던 자바스크립트 환경에 대한 이해도를 높였습니다.
|
|
161
|
+
- **Consumer to Producer**: 항상 라이브러리를 사용하기만 하던 입장에서, 직접 도구를 만들어 배포하는 생산자가 되어봄으로써 오픈소스 생태계의 순환 구조를 체감했습니다.
|
|
162
|
+
- **JavaScript to TypeScript (Next Step)**: 순수 ESM 환경을 구축하며 모듈 시스템은 표준화했으나, 컴파일 단계에서 오류를 잡을 수 없는 동적 타입 언어의 한계를 체감했습니다. 프로젝트의 안정성과 유지보수성을 높이기 위해 **TypeScript** 도입과 엄격한 타입 시스템의 필요성을 깨달았으며, 차기 버전에서는 이를 지원할 계획입니다.
|
|
163
|
+
|
|
164
|
+
## 🗺️ Roadmap (Future Plans)
|
|
165
|
+
|
|
166
|
+
- [ ] **TypeScript Support**: `tsconfig.json` 자동 설정 및 `.ts` 템플릿 지원
|
|
167
|
+
- [ ] **Test Environment**: Jest/Supertest 설정 자동화
|
|
168
|
+
|
|
169
|
+
## 📝 License
|
|
170
|
+
|
|
171
|
+
This project is MIT licensed.
|
package/bin/cli.js
CHANGED
|
@@ -1,100 +1,110 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
2
|
+
|
|
3
|
+
import { input, select } from '@inquirer/prompts'; // 현대적인 방식으로 교체
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
// ESM에서 __dirname 사용하기 위한 설정
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// 1.2.0 버전 정보 및 메인 로직
|
|
15
|
+
async function run() {
|
|
16
|
+
console.log(chalk.blue.bold('\n🚀 Create Express ESM 시작!\n'));
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// 1. 사용자 질문 (비동기 함수 방식으로 변경)
|
|
20
|
+
const projectName = await input({
|
|
21
|
+
message: '생성할 프로젝트 이름을 입력하세요:',
|
|
22
|
+
default: 'my-app',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const language = await select({
|
|
26
|
+
message: '사용할 언어를 선택하세요:',
|
|
27
|
+
choices: [
|
|
28
|
+
{ name: 'JavaScript (ESM)', value: 'js' },
|
|
29
|
+
{ name: 'TypeScript', value: 'ts' },
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const targetPath = path.join(process.cwd(), projectName);
|
|
34
|
+
const templatePath = path.join(__dirname, '../template', language);
|
|
35
|
+
|
|
36
|
+
// 2. 폴더 존재 여부 확인
|
|
37
|
+
if (fs.existsSync(targetPath)) {
|
|
38
|
+
console.error(chalk.red(`\n❌ 오류: '${projectName}' 폴더가 이미 존재합니다.`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 3. 템플릿 복사
|
|
43
|
+
console.log(chalk.cyan(`\n📂 [${language.toUpperCase()}] 템플릿을 복사하는 중...`));
|
|
44
|
+
|
|
45
|
+
if (!fs.existsSync(templatePath)) {
|
|
46
|
+
console.error(chalk.red(`\n❌ 오류: ${language} 템플릿 폴더를 찾을 수 없습니다.`));
|
|
47
|
+
console.log(chalk.gray(`경로 확인: ${templatePath}`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await fs.copy(templatePath, targetPath);
|
|
52
|
+
|
|
53
|
+
// 4. 도트 파일 변환 및 환경 설정
|
|
54
|
+
const renameMap = {
|
|
55
|
+
'gitignore': '.gitignore',
|
|
56
|
+
'_gitignore': '.gitignore',
|
|
57
|
+
'_env': '.env'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
for (const [oldName, newName] of Object.entries(renameMap)) {
|
|
61
|
+
const oldFilePath = path.join(targetPath, oldName);
|
|
62
|
+
const newFilePath = path.join(targetPath, newName);
|
|
63
|
+
|
|
64
|
+
if (await fs.pathExists(oldFilePath)) {
|
|
65
|
+
await fs.move(oldFilePath, newFilePath, { overwrite: true });
|
|
66
|
+
if (newName === '.env') {
|
|
67
|
+
const exampleEnvPath = path.join(targetPath, '.env.example');
|
|
68
|
+
await fs.copy(newFilePath, exampleEnvPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 5. package.json 프로젝트 이름 수정
|
|
74
|
+
const pkgPath = path.join(targetPath, 'package.json');
|
|
75
|
+
if (await fs.pathExists(pkgPath)) {
|
|
76
|
+
const pkg = await fs.readJson(pkgPath);
|
|
77
|
+
pkg.name = projectName;
|
|
78
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(chalk.green(`✅ 템플릿 구성 완료!`));
|
|
82
|
+
|
|
83
|
+
// 6. 패키지 자동 설치
|
|
84
|
+
console.log(chalk.yellow(`\n📦 패키지 자동 설치를 진행합니다... (npm install)`));
|
|
85
|
+
|
|
86
|
+
execSync('npm install', {
|
|
87
|
+
cwd: targetPath,
|
|
88
|
+
stdio: 'inherit'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log(chalk.green(`\n✨ 모든 설치가 완료되었습니다!`));
|
|
92
|
+
console.log(chalk.white(`\n다음 명령어로 시작하세요:\n`));
|
|
93
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
94
|
+
if (language === 'ts') {
|
|
95
|
+
console.log(chalk.cyan(` npm run dev (또는 npm run build)`));
|
|
96
|
+
} else {
|
|
97
|
+
console.log(chalk.cyan(` npm run dev`));
|
|
98
|
+
}
|
|
99
|
+
console.log('\n');
|
|
100
|
+
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error.name === 'ExitPromptError') {
|
|
103
|
+
console.log(chalk.yellow('\n\n👋 설치를 중단했습니다.'));
|
|
104
|
+
} else {
|
|
105
|
+
console.error(chalk.red('\n❌ 오류 발생:'), error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
run();
|
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-express-esm",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "A modern CLI tool to bootstrap Express.js applications with ES Modules and Layered Architecture.",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"create-express-esm": "bin/cli.js"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/munjuin/create-express-esm.git"
|
|
13
|
-
},
|
|
14
|
-
"scripts": {
|
|
15
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
16
|
-
"deploy": "npm version patch && git push origin main --tags && npm publish"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"express",
|
|
20
|
-
"esm",
|
|
21
|
-
"cli",
|
|
22
|
-
"boilerplate"
|
|
23
|
-
],
|
|
24
|
-
"author": "munjuin",
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"chalk": "^5.6.2",
|
|
28
|
-
"commander": "^14.0.2",
|
|
29
|
-
"fs-extra": "^11.3.2",
|
|
30
|
-
"inquirer": "^13.0.1"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"bin",
|
|
34
|
-
"template"
|
|
35
|
-
],
|
|
36
|
-
"bugs": {
|
|
37
|
-
"url": "https://github.com/munjuin/create-express-esm/issues"
|
|
38
|
-
},
|
|
39
|
-
"homepage": "https://github.com/munjuin/create-express-esm#readme"
|
|
40
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "create-express-esm",
|
|
3
|
+
"version": "1.1.4",
|
|
4
|
+
"description": "A modern CLI tool to bootstrap Express.js applications with ES Modules and Layered Architecture.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-express-esm": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/munjuin/create-express-esm.git"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
16
|
+
"deploy": "npm version patch && git push origin main --tags && npm publish"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"express",
|
|
20
|
+
"esm",
|
|
21
|
+
"cli",
|
|
22
|
+
"boilerplate"
|
|
23
|
+
],
|
|
24
|
+
"author": "munjuin",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^5.6.2",
|
|
28
|
+
"commander": "^14.0.2",
|
|
29
|
+
"fs-extra": "^11.3.2",
|
|
30
|
+
"inquirer": "^13.0.1"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"template"
|
|
35
|
+
],
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/munjuin/create-express-esm/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/munjuin/create-express-esm#readme"
|
|
40
|
+
}
|
package/template/js/_env
ADDED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-express-app",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"start": "node src/server.js",
|
|
7
|
-
"dev": "nodemon src/server.js"
|
|
8
|
-
},
|
|
9
|
-
"dependencies": {
|
|
10
|
-
"express": "^4.18.2",
|
|
11
|
-
"cors": "^2.8.5",
|
|
12
|
-
"dotenv": "^16.3.1",
|
|
13
|
-
"morgan": "^1.10.0",
|
|
14
|
-
"helmet": "^7.1.0"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"nodemon": "^3.0.2"
|
|
18
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "my-express-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node src/server.js",
|
|
7
|
+
"dev": "nodemon src/server.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"express": "^4.18.2",
|
|
11
|
+
"cors": "^2.8.5",
|
|
12
|
+
"dotenv": "^16.3.1",
|
|
13
|
+
"morgan": "^1.10.0",
|
|
14
|
+
"helmet": "^7.1.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"nodemon": "^3.0.2"
|
|
18
|
+
}
|
|
19
19
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import cors from 'cors';
|
|
3
|
-
import morgan from 'morgan';
|
|
4
|
-
import dotenv from 'dotenv';
|
|
5
|
-
|
|
6
|
-
import userRoutes from './routes/userRoutes.js';
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
|
|
10
|
-
app.use(cors());
|
|
11
|
-
app.use(morgan('dev'));
|
|
12
|
-
app.use(express.json());
|
|
13
|
-
|
|
14
|
-
// 라우터 연결 (Layered Arch 시작점)
|
|
15
|
-
app.use('/api/users', userRoutes);
|
|
16
|
-
|
|
17
|
-
app.get('/', (req, res) => res.send('Welcome to Modern Express!'));
|
|
18
|
-
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import morgan from 'morgan';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
|
|
6
|
+
import userRoutes from './routes/userRoutes.js';
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
|
|
10
|
+
app.use(cors());
|
|
11
|
+
app.use(morgan('dev'));
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
|
|
14
|
+
// 라우터 연결 (Layered Arch 시작점)
|
|
15
|
+
app.use('/api/users', userRoutes);
|
|
16
|
+
|
|
17
|
+
app.get('/', (req, res) => res.send('Welcome to Modern Express!'));
|
|
18
|
+
|
|
19
19
|
export default app;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as userService from '../services/userService.js';
|
|
2
|
-
|
|
3
|
-
export const getUsers = async (req, res) => {
|
|
4
|
-
try {
|
|
5
|
-
const users = await userService.findAllUsers(); // 서비스 호출
|
|
6
|
-
res.json(users);
|
|
7
|
-
} catch (error) {
|
|
8
|
-
res.status(500).json({ message: error.message });
|
|
9
|
-
}
|
|
1
|
+
import * as userService from '../services/userService.js';
|
|
2
|
+
|
|
3
|
+
export const getUsers = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const users = await userService.findAllUsers(); // 서비스 호출
|
|
6
|
+
res.json(users);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
res.status(500).json({ message: error.message });
|
|
9
|
+
}
|
|
10
10
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import * as userController from '../controllers/userController.js';
|
|
3
|
-
|
|
4
|
-
const router = express.Router();
|
|
5
|
-
|
|
6
|
-
router.get('/', userController.getUsers);
|
|
7
|
-
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import * as userController from '../controllers/userController.js';
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.get('/', userController.getUsers);
|
|
7
|
+
|
|
8
8
|
export default router;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import app from './app.js';
|
|
2
|
-
import dotenv from 'dotenv';
|
|
3
|
-
|
|
4
|
-
dotenv.config();
|
|
5
|
-
const PORT = process.env.PORT || 3000;
|
|
6
|
-
|
|
7
|
-
app.listen(PORT, () => {
|
|
8
|
-
console.log(`🚀 Server running on http://localhost:${PORT}`);
|
|
1
|
+
import app from './app.js';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
const PORT = process.env.PORT || 3000;
|
|
6
|
+
|
|
7
|
+
app.listen(PORT, () => {
|
|
8
|
+
console.log(`🚀 Server running on http://localhost:${PORT}`);
|
|
9
9
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// 나중에는 여기서 Model(DB)을 호출합니다.
|
|
2
|
-
export const findAllUsers = async () => {
|
|
3
|
-
// 비즈니스 로직 처리...
|
|
4
|
-
return [
|
|
5
|
-
{ id: 1, name: "Developer Lee", role: "Fullstack" },
|
|
6
|
-
{ id: 2, name: "Genius AI", role: "Assistant" }
|
|
7
|
-
];
|
|
1
|
+
// 나중에는 여기서 Model(DB)을 호출합니다.
|
|
2
|
+
export const findAllUsers = async () => {
|
|
3
|
+
// 비즈니스 로직 처리...
|
|
4
|
+
return [
|
|
5
|
+
{ id: 1, name: "Developer Lee", role: "Fullstack" },
|
|
6
|
+
{ id: 2, name: "Genius AI", role: "Assistant" }
|
|
7
|
+
];
|
|
8
8
|
};
|
package/template/ts/_env
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "temp-name",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "node dist/server.js",
|
|
7
|
+
"dev": "nodemon --exec ts-node src/server.ts",
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"express": "^4.18.2",
|
|
12
|
+
"dotenv": "^16.3.1",
|
|
13
|
+
"cors": "^2.8.5"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.0.0",
|
|
17
|
+
"@types/express": "^4.17.17",
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"@types/cors": "^2.8.13",
|
|
20
|
+
"ts-node": "^10.9.1",
|
|
21
|
+
"nodemon": "^3.0.1"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import express, { Application, Request, Response } from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import userRoutes from './routes/userRoutes.js';
|
|
4
|
+
|
|
5
|
+
const app: Application = express();
|
|
6
|
+
|
|
7
|
+
// Middleware
|
|
8
|
+
app.use(cors());
|
|
9
|
+
app.use(express.json());
|
|
10
|
+
app.use(express.urlencoded({ extended: true }));
|
|
11
|
+
|
|
12
|
+
// Routes
|
|
13
|
+
app.use('/api/users', userRoutes);
|
|
14
|
+
|
|
15
|
+
// Health Check
|
|
16
|
+
app.get('/', (req: Request, res: Response) => {
|
|
17
|
+
res.send('Create Express ESM (TypeScript) Server is Running!');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default app;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import * as userService from '../services/userService.js';
|
|
3
|
+
|
|
4
|
+
export const getUsers = async (req: Request, res: Response): Promise<void> => {
|
|
5
|
+
try {
|
|
6
|
+
const users = await userService.fetchAllUsers();
|
|
7
|
+
res.status(200).json({
|
|
8
|
+
success: true,
|
|
9
|
+
data: users,
|
|
10
|
+
});
|
|
11
|
+
} catch (error) {
|
|
12
|
+
res.status(500).json({
|
|
13
|
+
success: false,
|
|
14
|
+
message: 'Internal Server Error',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// 유저 데이터 타입 정의
|
|
2
|
+
interface User {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const fetchAllUsers = async (): Promise<User[]> => {
|
|
9
|
+
// 실제 DB 연동 대신 샘플 데이터를 반환합니다.
|
|
10
|
+
return [
|
|
11
|
+
{ id: 1, name: 'Owner', email: 'owner@example.com' },
|
|
12
|
+
{ id: 2, name: 'Gemini', email: 'gemini@ai.com' },
|
|
13
|
+
];
|
|
14
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules"]
|
|
15
|
+
}
|
package/template/_env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
PORT=8080
|
package/template/_gitignore
DELETED
|
File without changes
|