create-express-esm 1.1.2 โ†’ 1.1.3

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 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
- [![npm version](https://img.shields.io/npm/v/create-express-esm.svg)](https://www.npmjs.com/package/create-express-esm)
10
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
-
12
- ## โœจ Demo
13
-
14
- ![Image](https://github.com/user-attachments/assets/10a7c289-ba5b-42a0-bfb8-6319610fef78)
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
+ [![npm version](https://img.shields.io/npm/v/create-express-esm.svg)](https://www.npmjs.com/package/create-express-esm)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
+
12
+ ## โœจ Demo
13
+
14
+ ![Image](https://github.com/user-attachments/assets/10a7c289-ba5b-42a0-bfb8-6319610fef78)
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,100 @@
1
- #!/usr/bin/env node
2
-
3
- import { program } from 'commander';
4
- import inquirer from 'inquirer';
5
- import fs from 'fs-extra';
6
- import path from 'path';
7
- import chalk from 'chalk';
8
- import { execSync } from 'child_process';
9
- import { fileURLToPath } from 'url';
10
-
11
- // ESM์—์„œ __dirname ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = path.dirname(__filename);
14
-
15
- program
16
- .version('1.1.0') // ์ด์Šˆ ํ•ด๊ฒฐ์„ ๋ฐ˜์˜ํ•˜์—ฌ ๋ฒ„์ „ ์ƒํ–ฅ
17
- .description('Layered Architecture ๊ธฐ๋ฐ˜์˜ Modern Express ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ๊ธฐ');
18
-
19
- program
20
- .action(async () => {
21
- console.log(chalk.blue.bold('\n๐Ÿš€ Create Express ESM ์‹œ์ž‘!\n'));
22
-
23
- // 1. ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
24
- const answers = await inquirer.prompt([
25
- {
26
- type: 'input',
27
- name: 'projectName',
28
- message: '์ƒ์„ฑํ•  ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”:',
29
- default: 'my-app',
30
- }
31
- ]);
32
-
33
- const { projectName } = answers;
34
- const targetPath = path.join(process.cwd(), projectName);
35
- const templatePath = path.join(__dirname, '../template');
36
-
37
- // 2. ํ…œํ”Œ๋ฆฟ ๋ณต์‚ฌ ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
38
- try {
39
- if (fs.existsSync(targetPath)) {
40
- console.error(chalk.red(`โŒ ์˜ค๋ฅ˜: '${projectName}' ํด๋”๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.`));
41
- process.exit(1);
42
- }
43
-
44
- console.log(chalk.cyan(`\n๐Ÿ“‚ ํ…œํ”Œ๋ฆฟ์„ ๋ณต์‚ฌํ•˜๋Š” ์ค‘...`));
45
- await fs.copy(templatePath, targetPath);
46
-
47
- /**
48
- * [์ด์Šˆ #1 ํ•ด๊ฒฐ] ๋„ํŠธ ํŒŒ์ผ(Dotfiles) ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋กœ์ง
49
- * NPM ๋ฐฐํฌ ์‹œ ๋ฌด์‹œ๋˜๋Š” .gitignore์™€ .env๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
50
- */
51
- const renameMap = {
52
- 'gitignore': '.gitignore', // ๊ธฐ์กด ์‚ฌ์šฉ ๋ฐฉ์‹ ๋Œ€์‘
53
- '_gitignore': '.gitignore', // ์‹ ๊ทœ ๊ถŒ์žฅ ๋ฐฉ์‹ ๋Œ€์‘
54
- '_env': '.env' // .env ๋Œ€์‘
55
- };
56
-
57
- for (const [oldName, newName] of Object.entries(renameMap)) {
58
- const oldFilePath = path.join(targetPath, oldName);
59
- const newFilePath = path.join(targetPath, newName);
60
-
61
- if (await fs.pathExists(oldFilePath)) {
62
- await fs.move(oldFilePath, newFilePath, { overwrite: true });
63
-
64
- // .env๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ .env.example๋„ ํ•จ๊ป˜ ์ƒ์„ฑ (DX ๊ฐœ์„ )
65
- if (newName === '.env') {
66
- const exampleEnvPath = path.join(targetPath, '.env.example');
67
- await fs.copy(newFilePath, exampleEnvPath);
68
- }
69
- }
70
- }
71
-
72
- // 3. package.json ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ ์ˆ˜์ •
73
- const pkgPath = path.join(targetPath, 'package.json');
74
- if (await fs.pathExists(pkgPath)) {
75
- const pkg = await fs.readJson(pkgPath);
76
- pkg.name = projectName;
77
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
78
- }
79
-
80
- console.log(chalk.green(`โœ… ํ…œํ”Œ๋ฆฟ ๊ตฌ์„ฑ ๋ฐ ํ™˜๊ฒฝ ์„ค์ • ์™„๋ฃŒ!`));
81
-
82
- // 4. ํŒจํ‚ค์ง€ ์ž๋™ ์„ค์น˜
83
- console.log(chalk.yellow(`\n๐Ÿ“ฆ ํŒจํ‚ค์ง€ ์ž๋™ ์„ค์น˜๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค... (npm install)`));
84
-
85
- execSync('npm install', {
86
- cwd: targetPath,
87
- stdio: 'inherit'
88
- });
89
-
90
- console.log(chalk.green(`\nโœจ ๋ชจ๋“  ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!`));
91
- console.log(chalk.white(`\n๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”:\n`));
92
- console.log(chalk.cyan(` cd ${projectName}`));
93
- console.log(chalk.cyan(` npm run dev\n`));
94
-
95
- } catch (error) {
96
- console.error(chalk.red('\nโŒ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:'), error);
97
- }
98
- });
99
-
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import inquirer from 'inquirer';
5
+ import fs from 'fs-extra';
6
+ import path from 'path';
7
+ import chalk from 'chalk';
8
+ import { execSync } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ // ESM์—์„œ __dirname ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ program
16
+ .version('1.1.0') // ์ด์Šˆ ํ•ด๊ฒฐ์„ ๋ฐ˜์˜ํ•˜์—ฌ ๋ฒ„์ „ ์ƒํ–ฅ
17
+ .description('Layered Architecture ๊ธฐ๋ฐ˜์˜ Modern Express ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ๊ธฐ');
18
+
19
+ program
20
+ .action(async () => {
21
+ console.log(chalk.blue.bold('\n๐Ÿš€ Create Express ESM ์‹œ์ž‘!\n'));
22
+
23
+ // 1. ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
24
+ const answers = await inquirer.prompt([
25
+ {
26
+ type: 'input',
27
+ name: 'projectName',
28
+ message: '์ƒ์„ฑํ•  ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”:',
29
+ default: 'my-app',
30
+ }
31
+ ]);
32
+
33
+ const { projectName } = answers;
34
+ const targetPath = path.join(process.cwd(), projectName);
35
+ const templatePath = path.join(__dirname, '../template');
36
+
37
+ // 2. ํ…œํ”Œ๋ฆฟ ๋ณต์‚ฌ ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
38
+ try {
39
+ if (fs.existsSync(targetPath)) {
40
+ console.error(chalk.red(`โŒ ์˜ค๋ฅ˜: '${projectName}' ํด๋”๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.`));
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log(chalk.cyan(`\n๐Ÿ“‚ ํ…œํ”Œ๋ฆฟ์„ ๋ณต์‚ฌํ•˜๋Š” ์ค‘...`));
45
+ await fs.copy(templatePath, targetPath);
46
+
47
+ /**
48
+ * [์ด์Šˆ #1 ํ•ด๊ฒฐ] ๋„ํŠธ ํŒŒ์ผ(Dotfiles) ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋กœ์ง
49
+ * NPM ๋ฐฐํฌ ์‹œ ๋ฌด์‹œ๋˜๋Š” .gitignore์™€ .env๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
50
+ */
51
+ const renameMap = {
52
+ 'gitignore': '.gitignore', // ๊ธฐ์กด ์‚ฌ์šฉ ๋ฐฉ์‹ ๋Œ€์‘
53
+ '_gitignore': '.gitignore', // ์‹ ๊ทœ ๊ถŒ์žฅ ๋ฐฉ์‹ ๋Œ€์‘
54
+ '_env': '.env' // .env ๋Œ€์‘
55
+ };
56
+
57
+ for (const [oldName, newName] of Object.entries(renameMap)) {
58
+ const oldFilePath = path.join(targetPath, oldName);
59
+ const newFilePath = path.join(targetPath, newName);
60
+
61
+ if (await fs.pathExists(oldFilePath)) {
62
+ await fs.move(oldFilePath, newFilePath, { overwrite: true });
63
+
64
+ // .env๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ .env.example๋„ ํ•จ๊ป˜ ์ƒ์„ฑ (DX ๊ฐœ์„ )
65
+ if (newName === '.env') {
66
+ const exampleEnvPath = path.join(targetPath, '.env.example');
67
+ await fs.copy(newFilePath, exampleEnvPath);
68
+ }
69
+ }
70
+ }
71
+
72
+ // 3. package.json ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ ์ˆ˜์ •
73
+ const pkgPath = path.join(targetPath, 'package.json');
74
+ if (await fs.pathExists(pkgPath)) {
75
+ const pkg = await fs.readJson(pkgPath);
76
+ pkg.name = projectName;
77
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
78
+ }
79
+
80
+ console.log(chalk.green(`โœ… ํ…œํ”Œ๋ฆฟ ๊ตฌ์„ฑ ๋ฐ ํ™˜๊ฒฝ ์„ค์ • ์™„๋ฃŒ!`));
81
+
82
+ // 4. ํŒจํ‚ค์ง€ ์ž๋™ ์„ค์น˜
83
+ console.log(chalk.yellow(`\n๐Ÿ“ฆ ํŒจํ‚ค์ง€ ์ž๋™ ์„ค์น˜๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค... (npm install)`));
84
+
85
+ execSync('npm install', {
86
+ cwd: targetPath,
87
+ stdio: 'inherit'
88
+ });
89
+
90
+ console.log(chalk.green(`\nโœจ ๋ชจ๋“  ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!`));
91
+ console.log(chalk.white(`\n๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”:\n`));
92
+ console.log(chalk.cyan(` cd ${projectName}`));
93
+ console.log(chalk.cyan(` npm run dev\n`));
94
+
95
+ } catch (error) {
96
+ console.error(chalk.red('\nโŒ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:'), error);
97
+ }
98
+ });
99
+
100
100
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,27 +1,40 @@
1
- {
2
- "name": "create-express-esm",
3
- "version": "1.1.2",
4
- "description": "A modern CLI tool to bootstrap Express.js applications with ES Modules and Layered Architecture.",
5
- "main": "index.js",
6
- "bin": {
7
- "create-express-esm": "./bin/cli.js"
8
- },
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1",
11
- "deploy": "npm version patch && git push origin main --tags && npm publish"
12
- },
13
- "keywords": [],
14
- "author": "",
15
- "license": "ISC",
16
- "type": "module",
17
- "dependencies": {
18
- "chalk": "^5.6.2",
19
- "commander": "^14.0.2",
20
- "fs-extra": "^11.3.2",
21
- "inquirer": "^13.0.1"
22
- },
23
- "files": [
24
- "bin",
25
- "template"
26
- ]
27
- }
1
+ {
2
+ "name": "create-express-esm",
3
+ "version": "1.1.3",
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,4 +1,4 @@
1
- node_modules/
2
- .env
3
- .DS_Store
1
+ node_modules/
2
+ .env
3
+ .DS_Store
4
4
  template/node_modules/
@@ -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
  };