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 +171 -171
- package/bin/cli.js +99 -99
- package/package.json +40 -27
- package/template/_gitignore +3 -3
- package/template/package.json +18 -18
- package/template/src/app.js +18 -18
- package/template/src/controllers/userController.js +9 -9
- package/template/src/routes/userRoutes.js +7 -7
- package/template/src/server.js +8 -8
- package/template/src/services/userService.js +7 -7
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,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.
|
|
4
|
-
"description": "A modern CLI tool to bootstrap Express.js applications with ES Modules and Layered Architecture.",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
}
|
package/template/_gitignore
CHANGED
|
@@ -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/
|
package/template/package.json
CHANGED
|
@@ -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
|
}
|
package/template/src/app.js
CHANGED
|
@@ -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;
|
package/template/src/server.js
CHANGED
|
@@ -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
|
};
|