create-express-esm 1.1.11 β 1.2.0
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 +45 -20
- package/bin/cli.js +160 -109
- package/package.json +2 -2
- package/template/common/docker-compose.yml +16 -0
- package/template/common/prisma/schema.prisma +16 -0
- package/template/common/src/controllers/userController.js +0 -0
- package/template/common/src/controllers/userController.ts +42 -0
- package/template/common/src/lib/prisma.js +0 -0
- package/template/common/src/lib/prisma.ts +0 -0
- package/template/common/src/middlewares/errorMiddleware.ts +19 -0
- package/template/common/src/routes/userRoutes.js +0 -0
- package/template/common/src/routes/userRoutes.ts +0 -0
- package/template/common/src/services/userService.js +0 -0
- package/template/common/src/services/userService.ts +0 -0
- package/template/common/src/utils/appError.ts +12 -0
- package/template/js/_env +3 -2
- package/template/js/package-lock.json +3366 -0
- package/template/js/package.json +7 -2
- package/template/js/src/app.test.js +2 -0
- package/template/js/vitest.config.js +2 -0
- package/template/ts/_env +3 -0
- package/template/ts/package-lock.json +4020 -0
- package/template/ts/package.json +9 -3
- package/template/ts/src/app.test.ts +2 -0
- package/template/ts/vitest.config.ts +2 -0
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# π Create Express ESM (CLI)
|
|
2
2
|
|
|
3
|
-
> **Modern Express Generator**
|
|
3
|
+
> **Modern Express Generator with Database & Error Handling**
|
|
4
4
|
>
|
|
5
|
-
> "1μ΄ λ§μ μμ±νλ Modern Express(ESM) νκ²½"
|
|
5
|
+
> "1μ΄ λ§μ μμ±νλ Modern Express(ESM) + Prisma + Docker νκ²½"
|
|
6
6
|
>
|
|
7
|
-
> CommonJS(require)κ° μλ, μ΅μ ES Modules(import/export) λ¬Έλ²μ κΈ°λ°μΌλ‘
|
|
7
|
+
> CommonJS(require)κ° μλ, μ΅μ ES Modules(import/export) λ¬Έλ²μ κΈ°λ°μΌλ‘ νλ©°, λ°μ΄ν°λ² μ΄μ€ μ°λ λ° μ λ¬Έμ μΈ μλ¬ νΈλ€λ§κΉμ§ ν¬ν¨λ Express νλ‘μ νΈ κ΅¬μ‘°λ₯Ό μλμΌλ‘ μμ±ν΄μ£Όλ CLI λꡬμ
λλ€.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/create-express-esm)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
## β¨ Key Features (ν΅μ¬ κΈ°λ₯)
|
|
17
17
|
|
|
18
|
-
κΈ°μ‘΄ `express-generator`μ νκ³λ₯Ό λΆμνκ³ κ°μ νμ¬ κ°λ°νμ΅λλ€.
|
|
18
|
+
κΈ°μ‘΄ `express-generator`μ νκ³λ₯Ό λΆμνκ³ κ°μ νμ¬ κ°λ°νμ΅λλ€. v1.2.0 μ
λ°μ΄νΈλ₯Ό ν΅ν΄ μ€λ¬΄ν νμ€ν λ² μ΄μ€λ₯Ό μ 곡ν©λλ€.
|
|
19
19
|
|
|
20
20
|
- **π Multi-Language Support**: JavaScript(ESM)μ **TypeScript** μ€ μνλ κ°λ° νκ²½μ μ νν μ μμ΅λλ€.
|
|
21
|
+
- **ποΈ Database Integration**: **Prisma ORM**κ³Ό **PostgreSQL** νκ²½μ μ¦μ ꡬμΆν©λλ€. (Docker Compose μλ μμ±)
|
|
22
|
+
- **π¨ Professional Error Handling**: `AppError` ν΄λμ€μ **Global Error Middleware**λ₯Ό ν΅ν μ€μ μ§μ€μ μλ¬ μ²λ¦¬ μμ€ν
μ μ 곡ν©λλ€.
|
|
21
23
|
- **π§ͺ Integrated Testing**: μ΅μ ν
μ€νΈ νλ μμν¬μΈ **Vitest**μ API ν
μ€νΈμ© **Supertest** νκ²½μ μλ μ€μ ν©λλ€.
|
|
22
|
-
- **π Layered Architecture**: μ€λ¬΄ νμ€μΈ `Controller` - `Service` - `Route` κ³μΈ΅ ꡬ쑰λ₯Ό μ 곡ν©λλ€.
|
|
23
|
-
- **β‘οΈ Modern
|
|
24
|
+
- **π Layered Architecture**: μ€λ¬΄ νμ€μΈ `Controller` - `Service` - `Route` - `Middleware` κ³μΈ΅ ꡬ쑰λ₯Ό μ 곡ν©λλ€.
|
|
25
|
+
- **β‘οΈ Modern Execution**: `ts-node`μ ESM νΈνμ± λ¬Έμ λ₯Ό ν΄κ²°ν **`tsx`**λ₯Ό μ±ννμ¬ μΎμ ν κ°λ° νκ²½μ μ 곡ν©λλ€.
|
|
24
26
|
- **π¦ Smart Auto-Installation**: νλ‘μ νΈ μμ± μ¦μ μμ‘΄μ± μ€μΉ λ° νκ²½ λ³μ(`.env`) μΈν
μ μλ£ν©λλ€.
|
|
25
27
|
|
|
26
28
|
## π Quick Start (μ¬μ©λ²)
|
|
@@ -29,13 +31,29 @@
|
|
|
29
31
|
|
|
30
32
|
```bash
|
|
31
33
|
npx create-express-esm
|
|
32
|
-
npm install -g create-express-esm
|
|
33
|
-
npm install create-express-esm
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
### νλ‘μ νΈ μμ± ν DB μμνκΈ°
|
|
37
|
+
|
|
38
|
+
DB μ΅μ
μ μ ννλ€λ©΄, λ¨ λͺ μ€μ λͺ
λ Ήμ΄λ‘ κ°λ° μ€λΉκ° λλ©λλ€.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. νλ‘μ νΈ ν΄λ μ΄λ
|
|
42
|
+
cd my-app
|
|
43
|
+
|
|
44
|
+
# 2. Dockerλ₯Ό ν΅ν PostgreSQL μ€ν
|
|
45
|
+
npm run db:up
|
|
46
|
+
|
|
47
|
+
# 3. Prisma μ€ν€λ§λ₯Ό DBμ λ°μ (ν
μ΄λΈ μμ±)
|
|
48
|
+
npm run db:push
|
|
37
49
|
|
|
50
|
+
# 4. μλ² μ€ν
|
|
51
|
+
npm run dev
|
|
38
52
|
```
|
|
53
|
+
|
|
54
|
+
λλ μ μμΌλ‘ μ€μΉνμ¬ μ¬μ©ν μλ μμ΅λλ€.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
39
57
|
npm install -g create-express-esm
|
|
40
58
|
create-express-esm
|
|
41
59
|
```
|
|
@@ -43,36 +61,40 @@ create-express-esm
|
|
|
43
61
|
## π Project Structure (ν΄λ ꡬ쑰)
|
|
44
62
|
|
|
45
63
|
μ΄ λꡬλ **Layered Architecture (κ³μΈ΅ν μν€ν
μ²)**λ₯Ό κΈ°λ°μΌλ‘ νλ‘μ νΈλ₯Ό μμ±ν©λλ€.
|
|
46
|
-
|
|
64
|
+
κ΄μ¬μ¬ λΆλ¦¬(Separation of Concerns) μμΉμ μ μ©νμ¬ μ μ§λ³΄μκ° μ¬μ΄ ꡬ쑰λ₯Ό μ 곡ν©λλ€.
|
|
47
65
|
|
|
48
66
|
```text
|
|
49
67
|
my-app/
|
|
68
|
+
βββ prisma/ # ποΈ Prisma Schema & Migrations
|
|
50
69
|
βββ src/
|
|
51
70
|
β βββ controllers/ # πΉοΈ μμ² μ²λ¦¬ λ° μλ΅ λ°ν (Controller Layer)
|
|
52
71
|
β βββ services/ # π§ λΉμ¦λμ€ λ‘μ§ μ²λ¦¬ (Service Layer)
|
|
53
72
|
β βββ routes/ # π¦ API μλν¬μΈνΈ μ μ (Route Layer)
|
|
54
|
-
β βββ
|
|
55
|
-
β βββ
|
|
73
|
+
β βββ middlewares/ # π‘οΈ μ μ μλ¬ νΈλ€λ¬ λ° μ»€μ€ν
λ―Έλ€μ¨μ΄
|
|
74
|
+
β βββ utils/ # π οΈ AppError ν΄λμ€ λ± κ³΅ν΅ μ νΈλ¦¬ν°
|
|
75
|
+
β βββ lib/ # ποΈ Prisma Client μΈμ€ν΄μ€ (Singleton)
|
|
76
|
+
β βββ app.ts # ποΈ Express μ± μ€μ λ° λ―Έλ€μ¨μ΄
|
|
77
|
+
β βββ server.ts # π μλ² μ§μ
μ (Entry Point)
|
|
56
78
|
β βββ app.test.ts # π§ͺ Vitest μν ν
μ€νΈ μ½λ
|
|
57
|
-
βββ .env # π νκ²½ λ³μ (μλ μμ±)
|
|
79
|
+
βββ .env # π νκ²½ λ³μ (DATABASE_URL μλ μμ±)
|
|
80
|
+
βββ docker-compose.yml # π³ PostgreSQL 컨ν
μ΄λ μ€μ
|
|
58
81
|
βββ vitest.config.ts # π§ͺ Vitest μ€μ νμΌ
|
|
59
82
|
βββ tsconfig.json # βοΈ TS μ»΄νμΌλ¬ μ€μ (TS μ ν μ)
|
|
60
83
|
βββ package.json # π¦ μμ‘΄μ± λ° μ€ν¬λ¦½νΈ
|
|
61
84
|
```
|
|
62
85
|
|
|
63
|
-
μ£ΌμΈλ, μμ²νμ λλ‘ π Tech Stack (κΈ°μ μ€ν) μΉμ
λΆν° λ§μ§λ§κΉμ§μ λ΄μ©μ λ§ν¬λ€μ΄ μ½λλ‘ μ λ¦¬ν΄ λ립λλ€. μ΄ λΆλΆμ μ£ΌμΈλμ΄ μ΄λ²μ ν΄κ²°νμ κΈ°μ μ λμ κ³Όμ λ€μ΄ κ³ μ€λν λ΄κ²¨ μμ΄ ν¬νΈν΄λ¦¬μ€λ‘μμ κ°μΉκ° λ§€μ° λμ΅λλ€.
|
|
64
|
-
|
|
65
|
-
Markdown
|
|
66
|
-
|
|
67
86
|
## π Tech Stack (κΈ°μ μ€ν)
|
|
68
87
|
|
|
69
88
|
- **Runtime**: Node.js (v20+)
|
|
70
89
|
- **Framework**: Express.js
|
|
71
90
|
- **Language**: JavaScript (ESM) / TypeScript 5.x
|
|
91
|
+
- **ORM**: Prisma (PostgreSQL)
|
|
92
|
+
- **Infrastructure**: Docker Compose
|
|
72
93
|
- **Testing**: Vitest, Supertest
|
|
73
94
|
- **Dev Tools**:
|
|
74
95
|
- `tsx` (TypeScript Execution Engine)
|
|
75
96
|
- `nodemon` (Hot Reload)
|
|
97
|
+
- `@clack/prompts` (Interactive CLI UI)
|
|
76
98
|
- `dotenv` (Environment Variables)
|
|
77
99
|
- `cors` (Cross-Origin Resource Sharing)
|
|
78
100
|
- `chalk` (CLI Styling)
|
|
@@ -80,15 +102,18 @@ Markdown
|
|
|
80
102
|
## π Retrospective
|
|
81
103
|
|
|
82
104
|
- **νμ€νλ νκ²½μ μ€μμ±**: CJSμμ ESMμΌλ‘ λμ΄κ°λ κ³ΌλκΈ°μ λ¬Έμ λ₯Ό ν΄κ²°νλ©° λͺ¨λ μλ°μ€ν¬λ¦½νΈ λͺ¨λ μμ€ν
μ λν κΉμ μ΄ν΄λ₯Ό μ»μμ΅λλ€.
|
|
105
|
+
- **μλ¬ νΈλ€λ§μ μ€μν**: κ°λ³ 컨νΈλ‘€λ¬μμ λ°λ³΅λλ μλ¬ μ²λ¦¬ λ‘μ§μ μ μ λ―Έλ€μ¨μ΄λ‘ μμνμ¬ μ½λ κ°λ
μ±κ³Ό μ μ§λ³΄μμ±μ κ·Ήλννμ΅λλ€.
|
|
106
|
+
- **μΈνλΌ νκ²½ μ΄μ ν΄κ²°**: λ‘컬 PostgreSQLκ³Όμ ν¬νΈ μΆ©λ(5432 vs 5433) λ° λ컀 λ³Όλ₯¨ μΈμ¦ λ¬Έμ λ₯Ό ν΄κ²°νλ©°, μ¬μ©μμκ² κ°μ₯ μμ μ μΈ DB μ°κ²° κ°μ΄λλ₯Ό μ 곡νλ λ° μ±κ³΅νμ΅λλ€.
|
|
83
107
|
- **UX κΈ°λ° μ€κ³**: μ¬μ©μκ° νλ‘μ νΈλ₯Ό μμ±νμλ§μ `npm run dev`μ `npm test`λ₯Ό μ¦μ μ€νν μ μλ "Zero-Config" νκ²½μ μ 곡νλ λ° μ§μ€νμ΅λλ€.
|
|
84
|
-
- **λ°°ν¬ νλ‘μΈμ€μ μ±μ**: μλ λ°°ν¬μ μνμ±μ CI/CDμ OIDC λμ
μ ν΅ν΄ μλννλ©° μννΈμ¨μ΄ λ¦΄λ¦¬μ€ κ³Όμ μ μμ μ±μ ν보νμ΅λλ€.
|
|
85
108
|
|
|
86
109
|
## πΊοΈ Roadmap (Future Plans)
|
|
87
110
|
|
|
88
111
|
- [x] **TypeScript Support**: `.ts` ν
νλ¦Ώ λ° `tsx` νκ²½ μ΅μ ν
|
|
89
112
|
- [x] **Test Environment**: Vitest λ° Supertest μ€μ μλν
|
|
90
|
-
- [
|
|
91
|
-
- [
|
|
113
|
+
- [x] **Interactive UI Upgrade**: `Clack` λΌμ΄λΈλ¬λ¦¬λ₯Ό ν΅ν μκ°μ CLI UI κ°μ
|
|
114
|
+
- [x] **Database Integration**: Prisma/PostgreSQL λ° Docker μ ν μ΅μ
μΆκ°
|
|
115
|
+
- [ ] **Authentication Template**: JWT/Passportλ₯Ό μ΄μ©ν κΈ°λ³Έ μΈμ¦ λ‘μ§ μΆκ°
|
|
116
|
+
- [ ] **Deployment Guide**: AWS/Render λ± μ£Όμ νλ«νΌ λ°°ν¬ κ°μ΄λλΌμΈ μΆκ°
|
|
92
117
|
|
|
93
118
|
## π License
|
|
94
119
|
|
package/bin/cli.js
CHANGED
|
@@ -1,52 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
|
|
10
|
+
// ESM νκ²½μμ __dirname ꡬν
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
|
|
13
14
|
async function run() {
|
|
14
|
-
console.
|
|
15
|
+
console.clear();
|
|
16
|
+
|
|
17
|
+
// 1. μμ μΈμ¬
|
|
18
|
+
p.intro(`${chalk.bgBlue.white(' create-express-esm ')} ${chalk.dim('v1.2.0-beta')}`);
|
|
15
19
|
|
|
16
20
|
try {
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
// 2. μ¬μ©μ μ§λ¬Έ κ·Έλ£Ή
|
|
22
|
+
const project = await p.group(
|
|
23
|
+
{
|
|
24
|
+
projectName: () =>
|
|
25
|
+
p.text({
|
|
26
|
+
message: 'νλ‘μ νΈ μ΄λ¦μ μ
λ ₯νμΈμ:',
|
|
27
|
+
placeholder: 'my-app',
|
|
28
|
+
validate: (value) => {
|
|
29
|
+
if (value.length === 0) return 'νλ‘μ νΈ μ΄λ¦μ νμμ
λλ€!';
|
|
30
|
+
if (fs.existsSync(path.join(process.cwd(), value))) return 'ν΄λΉ ν΄λκ° μ΄λ―Έ μ‘΄μ¬ν©λλ€.';
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
language: () =>
|
|
34
|
+
p.select({
|
|
35
|
+
message: 'μ¬μ©ν μΈμ΄λ₯Ό μ ννμΈμ:',
|
|
36
|
+
options: [
|
|
37
|
+
{ value: 'js', label: 'JavaScript (ESM)' },
|
|
38
|
+
{ value: 'ts', label: 'TypeScript' },
|
|
39
|
+
],
|
|
40
|
+
}),
|
|
41
|
+
useTest: () =>
|
|
42
|
+
p.confirm({
|
|
43
|
+
message: 'Vitest ν
μ€νΈ νκ²½μ μΆκ°νμκ² μ΅λκΉ?',
|
|
44
|
+
initialValue: true,
|
|
45
|
+
}),
|
|
46
|
+
useDb: () =>
|
|
47
|
+
p.confirm({
|
|
48
|
+
message: 'Prisma ORM (PostgreSQL) λ° μ μ μλ¬ νΈλ€λ§μ μΆκ°νμκ² μ΅λκΉ?',
|
|
49
|
+
initialValue: false,
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
onCancel: () => {
|
|
54
|
+
p.cancel('νλ‘μ νΈ μμ±μ΄ μ·¨μλμμ΅λλ€.');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
);
|
|
35
59
|
|
|
60
|
+
const { projectName, language, useTest, useDb } = project;
|
|
36
61
|
const targetPath = path.join(process.cwd(), projectName);
|
|
37
62
|
const templatePath = path.join(__dirname, '../template', language);
|
|
63
|
+
const commonPath = path.join(__dirname, '../template/common');
|
|
38
64
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
65
|
+
// 3. νμΌ κ΅¬μ± μμ
|
|
66
|
+
const s = p.spinner();
|
|
67
|
+
s.start('νλ‘μ νΈ ν
νλ¦Ώμ 볡μ¬νλ μ€...');
|
|
44
68
|
|
|
45
|
-
//
|
|
46
|
-
console.log(chalk.cyan(`\nπ [${language.toUpperCase()}] ν
νλ¦Ώ ꡬμ±μ μμν©λλ€...`));
|
|
69
|
+
// κΈ°λ³Έ μΈμ΄ ν
νλ¦Ώ 볡μ¬
|
|
47
70
|
await fs.copy(templatePath, targetPath);
|
|
48
71
|
|
|
49
|
-
//
|
|
72
|
+
// λνΈ νμΌ λ³ν (_env -> .env λ±)
|
|
50
73
|
const renameMap = {
|
|
51
74
|
'gitignore': '.gitignore',
|
|
52
75
|
'_gitignore': '.gitignore',
|
|
@@ -55,109 +78,137 @@ async function run() {
|
|
|
55
78
|
|
|
56
79
|
for (const [oldName, newName] of Object.entries(renameMap)) {
|
|
57
80
|
const oldFilePath = path.join(targetPath, oldName);
|
|
58
|
-
const newFilePath = path.join(targetPath, newName);
|
|
59
81
|
if (await fs.pathExists(oldFilePath)) {
|
|
60
|
-
await fs.move(oldFilePath,
|
|
82
|
+
await fs.move(oldFilePath, path.join(targetPath, newName), { overwrite: true });
|
|
61
83
|
if (newName === '.env') {
|
|
62
|
-
await fs.copy(
|
|
84
|
+
await fs.copy(path.join(targetPath, '.env'), path.join(targetPath, '.env.example'));
|
|
63
85
|
}
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
|
-
|
|
67
|
-
//
|
|
88
|
+
|
|
89
|
+
// 4. DB λ° μλ¬ νΈλ€λ§ μ ν μ μΆκ° νμΌ λ³΅μ¬ λ° μ½λ μ£Όμ
|
|
90
|
+
if (useDb) {
|
|
91
|
+
// (1) Prisma μ€μ λ° Docker Compose 볡μ¬
|
|
92
|
+
await fs.copy(path.join(commonPath, 'prisma'), path.join(targetPath, 'prisma'));
|
|
93
|
+
await fs.copy(path.join(commonPath, 'docker-compose.yml'), path.join(targetPath, 'docker-compose.yml'));
|
|
94
|
+
|
|
95
|
+
// (2) μμ€ μ½λ λ³΅μ¬ (lib, services, controllers, routes, utils, middlewares)
|
|
96
|
+
const sourceFolders = ['lib', 'services', 'controllers', 'routes', 'utils', 'middlewares'];
|
|
97
|
+
for (const folder of sourceFolders) {
|
|
98
|
+
const srcFolderPath = path.join(commonPath, 'src', folder);
|
|
99
|
+
const destFolderPath = path.join(targetPath, 'src', folder);
|
|
100
|
+
|
|
101
|
+
if (await fs.pathExists(srcFolderPath)) {
|
|
102
|
+
await fs.ensureDir(destFolderPath);
|
|
103
|
+
const files = await fs.readdir(srcFolderPath);
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
// μ¬μ©μκ° μ νν μΈμ΄(ts/js)μ μΌμΉνλ νμΌλ§ 볡μ¬
|
|
106
|
+
if (file.endsWith(`.${language}`)) {
|
|
107
|
+
await fs.copy(path.join(srcFolderPath, file), path.join(destFolderPath, file));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// (3) app.ts / app.js μ μ½λ μ£Όμ
(μ€μ!)
|
|
114
|
+
const mainFilePath = path.join(targetPath, `src/app.${language}`);
|
|
115
|
+
if (await fs.pathExists(mainFilePath)) {
|
|
116
|
+
let content = await fs.readFile(mainFilePath, 'utf-8');
|
|
117
|
+
|
|
118
|
+
// μλ¨ μν¬νΈ μ£Όμ
|
|
119
|
+
const imports = [
|
|
120
|
+
`import userRoutes from './routes/userRoutes.js';`,
|
|
121
|
+
`import { errorHandler } from './middlewares/errorMiddleware.js';`
|
|
122
|
+
].join('\n');
|
|
123
|
+
content = imports + '\n' + content;
|
|
124
|
+
|
|
125
|
+
// λΌμ°ν° λ±λ‘ μ£Όμ
(express.json() λ€μ)
|
|
126
|
+
const routeCode = `\napp.use('/users', userRoutes);`;
|
|
127
|
+
content = content.replace('app.use(express.json());', `app.use(express.json());${routeCode}`);
|
|
128
|
+
|
|
129
|
+
// μ μ μλ¬ νΈλ€λ¬ μ£Όμ
(μλ² μ€ν μ§μ μ)
|
|
130
|
+
const errorMiddlewareCode = `\n// μ μ μλ¬ νΈλ€λ¬ (λͺ¨λ λΌμ°ν° λ€μμ μμΉν΄μΌ ν¨)\napp.use(errorHandler);\n`;
|
|
131
|
+
if (content.includes('export default app;')) {
|
|
132
|
+
content = content.replace('export default app;', `${errorMiddlewareCode}\nexport default app;`);
|
|
133
|
+
} else {
|
|
134
|
+
content += `\n${errorMiddlewareCode}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await fs.writeFile(mainFilePath, content);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// (4) .env νμΌμ DATABASE_URL μΆκ°
|
|
141
|
+
const envPath = path.join(targetPath, '.env');
|
|
142
|
+
const dbUrlContent = `
|
|
143
|
+
# PostgreSQL Connection (Docker Compose default)
|
|
144
|
+
DATABASE_URL="postgresql://myuser:mypassword@localhost:5432/mydb?schema=public"
|
|
145
|
+
`;
|
|
146
|
+
await fs.appendFile(envPath, dbUrlContent);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 5. package.json λμ μ΅μ ν
|
|
68
150
|
const pkgPath = path.join(targetPath, 'package.json');
|
|
69
151
|
const pkg = await fs.readJson(pkgPath);
|
|
70
152
|
pkg.name = projectName;
|
|
71
153
|
|
|
72
|
-
// [μΆκ°λ λΆλΆ] TypeScript νκ²½μμ ESM μλ¬λ₯Ό λ°©μ§νκΈ° μν tsx μ€μ
|
|
73
154
|
if (language === 'ts') {
|
|
74
|
-
console.log(chalk.yellow(`βοΈ TypeScript ESM μ€ν νκ²½(tsx)μ μ΅μ ννλ μ€...`));
|
|
75
|
-
|
|
76
|
-
// ts-node λμ tsxλ₯Ό μ¬μ©νμ¬ .js νμ₯μ μν¬νΈ λ¬Έμ ν΄κ²°
|
|
77
155
|
pkg.scripts.dev = "nodemon --exec tsx src/server.ts";
|
|
78
|
-
|
|
79
|
-
// μμ‘΄μ± κ΅μ²΄
|
|
80
|
-
pkg.devDependencies = {
|
|
81
|
-
...pkg.devDependencies,
|
|
82
|
-
"tsx": "^4.7.0"
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// κΈ°μ‘΄μ ts-nodeκ° μλ€λ©΄ μ κ±° (μ€λ³΅ λ°©μ§)
|
|
86
|
-
delete pkg.devDependencies['ts-node'];
|
|
156
|
+
pkg.devDependencies["tsx"] = "^4.7.0";
|
|
87
157
|
}
|
|
88
158
|
|
|
89
|
-
//
|
|
90
|
-
if (useTest) {
|
|
91
|
-
console.log(chalk.yellow(`π§ͺ Vitest μ€μ λ° μν ν
μ€νΈλ₯Ό μμ±νλ μ€...`));
|
|
92
|
-
|
|
93
|
-
pkg.scripts = {
|
|
94
|
-
...pkg.scripts,
|
|
95
|
-
"test": "vitest",
|
|
96
|
-
"test:ui": "vitest --ui",
|
|
97
|
-
"test:run": "vitest run"
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const testDeps = {
|
|
101
|
-
"vitest": "^1.0.0",
|
|
102
|
-
"supertest": "^6.3.3"
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
if (language === 'ts') {
|
|
106
|
-
testDeps["@types/supertest"] = "^2.0.12";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
pkg.devDependencies = {
|
|
110
|
-
...pkg.devDependencies,
|
|
111
|
-
...testDeps
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// Vitest μ€μ νμΌ μμ±
|
|
159
|
+
// ν
μ€νΈ νκ²½ μ€μ (λΉμ¬μ© μ κ΄λ ¨ νμΌ λ° ν¨ν€μ§ μ κ±°)
|
|
160
|
+
if (!useTest) {
|
|
115
161
|
const configExt = language === 'ts' ? 'ts' : 'js';
|
|
116
|
-
const configContent = `import { defineConfig } from 'vitest/config';
|
|
117
|
-
|
|
118
|
-
export default defineConfig({
|
|
119
|
-
test: {
|
|
120
|
-
globals: true,
|
|
121
|
-
environment: 'node',
|
|
122
|
-
},
|
|
123
|
-
});`;
|
|
124
|
-
await fs.writeFile(path.join(targetPath, `vitest.config.${configExt}`), configContent);
|
|
125
|
-
|
|
126
|
-
// μν ν
μ€νΈ νμΌ μμ±
|
|
127
162
|
const testFileExt = language === 'ts' ? 'ts' : 'js';
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
163
|
+
await fs.remove(path.join(targetPath, `vitest.config.${configExt}`));
|
|
164
|
+
await fs.remove(path.join(targetPath, `src/app.test.${testFileExt}`));
|
|
165
|
+
delete pkg.scripts.test;
|
|
166
|
+
delete pkg.scripts["test:ui"];
|
|
167
|
+
delete pkg.scripts["test:run"];
|
|
168
|
+
delete pkg.devDependencies.vitest;
|
|
169
|
+
delete pkg.devDependencies.supertest;
|
|
170
|
+
if (pkg.devDependencies["@types/supertest"]) delete pkg.devDependencies["@types/supertest"];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// DB μμ‘΄μ± λ° μ€ν¬λ¦½νΈ μΆκ°
|
|
174
|
+
if (useDb) {
|
|
175
|
+
pkg.scripts["db:up"] = "docker-compose up -d";
|
|
176
|
+
pkg.scripts["db:push"] = "prisma db push";
|
|
177
|
+
pkg.scripts["prisma:generate"] = "prisma generate";
|
|
178
|
+
pkg.scripts["prisma:studio"] = "prisma studio";
|
|
179
|
+
|
|
180
|
+
pkg.dependencies["@prisma/client"] = "^5.0.0";
|
|
181
|
+
pkg.devDependencies["prisma"] = "^5.0.0";
|
|
140
182
|
}
|
|
141
183
|
|
|
142
184
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
143
|
-
|
|
185
|
+
s.stop('νμΌ κ΅¬μ± μλ£!');
|
|
144
186
|
|
|
145
|
-
// 6.
|
|
146
|
-
|
|
147
|
-
|
|
187
|
+
// 6. μμ‘΄μ± μ€μΉ
|
|
188
|
+
const installSpinner = p.spinner();
|
|
189
|
+
installSpinner.start('μμ‘΄μ± ν¨ν€μ§λ₯Ό μ€μΉνλ μ€... (npm install)');
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
execSync('npm install', { cwd: targetPath, stdio: 'ignore' });
|
|
193
|
+
installSpinner.stop('μ€μΉ μλ£!');
|
|
194
|
+
} catch (e) {
|
|
195
|
+
installSpinner.stop(chalk.red('μ€μΉ μ€ν¨ (μλ μ€μΉκ° νμν μ μμ΅λλ€)'));
|
|
196
|
+
}
|
|
148
197
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
198
|
+
// 7. λ§λ¬΄λ¦¬
|
|
199
|
+
let nextSteps = `cd ${projectName}\n`;
|
|
200
|
+
if (useDb) {
|
|
201
|
+
nextSteps += `${chalk.bold('npm run db:up')} (DB μ€ν)\n`;
|
|
202
|
+
nextSteps += `${chalk.bold('npm run db:push')} (ν
μ΄λΈ μμ±)\n`;
|
|
203
|
+
}
|
|
204
|
+
nextSteps += `npm run dev`;
|
|
205
|
+
|
|
206
|
+
p.note(chalk.cyan(nextSteps), 'μμνλ €λ©΄ λ€μ λͺ
λ Ήμ΄λ₯Ό μ
λ ₯νμΈμ');
|
|
207
|
+
p.outro(chalk.green('β¨ λͺ¨λ μ€λΉκ° λλ¬μ΅λλ€. μ¦κ±°μ΄ κ°λ° λμΈμ!'));
|
|
154
208
|
|
|
155
209
|
} catch (error) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
} else {
|
|
159
|
-
console.error(chalk.red('\nβ μ€λ₯ λ°μ:'), error);
|
|
160
|
-
}
|
|
210
|
+
p.cancel(`μ€λ₯ λ°μ: ${error.message}`);
|
|
211
|
+
process.exit(1);
|
|
161
212
|
}
|
|
162
213
|
}
|
|
163
214
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-express-esm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A modern CLI tool to bootstrap Express.js applications with ES Modules and Layered Architecture.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"author": "munjuin",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@
|
|
28
|
+
"@clack/prompts": "^0.11.0",
|
|
29
29
|
"chalk": "^5.6.2",
|
|
30
30
|
"commander": "^14.0.2",
|
|
31
31
|
"fs-extra": "^11.3.2",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
services:
|
|
3
|
+
db:
|
|
4
|
+
image: postgres:15-alpine
|
|
5
|
+
restart: always
|
|
6
|
+
environment:
|
|
7
|
+
- POSTGRES_USER=myuser
|
|
8
|
+
- POSTGRES_PASSWORD=mypassword
|
|
9
|
+
- POSTGRES_DB=mydb
|
|
10
|
+
ports:
|
|
11
|
+
- '5432:5432'
|
|
12
|
+
volumes:
|
|
13
|
+
- db-data:/var/lib/postgresql/data
|
|
14
|
+
|
|
15
|
+
volumes:
|
|
16
|
+
db-data:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model User {
|
|
11
|
+
id Int @id @default(autoincrement())
|
|
12
|
+
email String @unique
|
|
13
|
+
name String?
|
|
14
|
+
createdAt DateTime @default(now())
|
|
15
|
+
updatedAt DateTime @updatedAt
|
|
16
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { userService } from '../services/userService.js';
|
|
3
|
+
|
|
4
|
+
export const userController = {
|
|
5
|
+
// 1. λͺ¨λ μ¬μ©μ μ‘°ν
|
|
6
|
+
async getUsers(req: Request, res: Response, next: NextFunction) {
|
|
7
|
+
try {
|
|
8
|
+
const users = await userService.getAllUsers();
|
|
9
|
+
res.json(users);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
next(error); // μλ¬λ₯Ό μ μ νΈλ€λ¬λ‘ λμ§
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// 2. μ μ¬μ©μ μμ±
|
|
16
|
+
async createUser(req: Request, res: Response, next: NextFunction) {
|
|
17
|
+
try {
|
|
18
|
+
const { email, name } = req.body;
|
|
19
|
+
const newUser = await userService.createUser({ email, name });
|
|
20
|
+
res.status(201).json(newUser);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
next(error); // μ¬κΈ°μ μλ¬κ° λμ Έμ§λ©΄ errorHandlerκ° P2002(μ€λ³΅) λ±μ μ²λ¦¬ν¨
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// 3. νΉμ μ¬μ©μ μ‘°ν
|
|
27
|
+
async getUserById(req: Request, res: Response, next: NextFunction) {
|
|
28
|
+
try {
|
|
29
|
+
const id = parseInt(req.params.id);
|
|
30
|
+
const user = await userService.getUserById(id);
|
|
31
|
+
|
|
32
|
+
if (!user) {
|
|
33
|
+
// AppErrorλ₯Ό μ¬μ©ν΄ 404 μλ¬λ₯Ό μ§μ λμ§ μλ μμ΅λλ€.
|
|
34
|
+
throw new Error('μ¬μ©μλ₯Ό μ°Ύμ μ μμ΅λλ€.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
res.json(user);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
next(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { AppError } from '../utils/appError.js';
|
|
3
|
+
|
|
4
|
+
export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
|
|
5
|
+
err.statusCode = err.statusCode || 500;
|
|
6
|
+
|
|
7
|
+
// Prisma κ³ μ μλ¬ μ²λ¦¬ (μ: μ΄λ©μΌ μ€λ³΅)
|
|
8
|
+
if (err.code === 'P2002') {
|
|
9
|
+
err.message = 'μ΄λ―Έ μ¬μ© μ€μΈ λ°μ΄ν°(μ΄λ©μΌ λ±)κ° μμ΅λλ€.';
|
|
10
|
+
err.statusCode = 400;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
res.status(err.statusCode).json({
|
|
14
|
+
status: 'error',
|
|
15
|
+
message: err.message,
|
|
16
|
+
// κ°λ° νκ²½μμλ§ μλ¬ μ€ν νμ
|
|
17
|
+
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class AppError extends Error {
|
|
2
|
+
public statusCode: number;
|
|
3
|
+
public isOperational: boolean;
|
|
4
|
+
|
|
5
|
+
constructor(message: string, statusCode: number) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.isOperational = true; // μμΈ‘ κ°λ₯ν μλ¬μμ νμ
|
|
9
|
+
|
|
10
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
}
|
package/template/js/_env
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# PostgreSQL Connection String
|
|
2
|
+
# νμ: postgresql://μ¬μ©μ:λΉλ°λ²νΈ@νΈμ€νΈ:ν¬νΈ/λ°μ΄ν°λ² μ΄μ€λͺ
?schema=public
|
|
3
|
+
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/myapp?schema=public"
|