create-express-esm 1.1.12 β 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 +46 -19
- package/bin/cli.js +98 -29
- package/package.json +1 -1
- 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/ts/_env +3 -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,48 +31,70 @@
|
|
|
29
31
|
|
|
30
32
|
```bash
|
|
31
33
|
npx create-express-esm
|
|
32
|
-
create-express-esm
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
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
|
|
36
49
|
|
|
50
|
+
# 4. μλ² μ€ν
|
|
51
|
+
npm run dev
|
|
37
52
|
```
|
|
53
|
+
|
|
54
|
+
λλ μ μμΌλ‘ μ€μΉνμ¬ μ¬μ©ν μλ μμ΅λλ€.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
38
57
|
npm install -g create-express-esm
|
|
58
|
+
create-express-esm
|
|
39
59
|
```
|
|
40
60
|
|
|
41
61
|
## π Project Structure (ν΄λ ꡬ쑰)
|
|
42
62
|
|
|
43
63
|
μ΄ λꡬλ **Layered Architecture (κ³μΈ΅ν μν€ν
μ²)**λ₯Ό κΈ°λ°μΌλ‘ νλ‘μ νΈλ₯Ό μμ±ν©λλ€.
|
|
44
|
-
|
|
64
|
+
κ΄μ¬μ¬ λΆλ¦¬(Separation of Concerns) μμΉμ μ μ©νμ¬ μ μ§λ³΄μκ° μ¬μ΄ ꡬ쑰λ₯Ό μ 곡ν©λλ€.
|
|
45
65
|
|
|
46
66
|
```text
|
|
47
67
|
my-app/
|
|
68
|
+
βββ prisma/ # ποΈ Prisma Schema & Migrations
|
|
48
69
|
βββ src/
|
|
49
70
|
β βββ controllers/ # πΉοΈ μμ² μ²λ¦¬ λ° μλ΅ λ°ν (Controller Layer)
|
|
50
71
|
β βββ services/ # π§ λΉμ¦λμ€ λ‘μ§ μ²λ¦¬ (Service Layer)
|
|
51
72
|
β βββ routes/ # π¦ API μλν¬μΈνΈ μ μ (Route Layer)
|
|
52
|
-
β βββ
|
|
53
|
-
β βββ
|
|
73
|
+
β βββ middlewares/ # π‘οΈ μ μ μλ¬ νΈλ€λ¬ λ° μ»€μ€ν
λ―Έλ€μ¨μ΄
|
|
74
|
+
β βββ utils/ # π οΈ AppError ν΄λμ€ λ± κ³΅ν΅ μ νΈλ¦¬ν°
|
|
75
|
+
β βββ lib/ # ποΈ Prisma Client μΈμ€ν΄μ€ (Singleton)
|
|
76
|
+
β βββ app.ts # ποΈ Express μ± μ€μ λ° λ―Έλ€μ¨μ΄
|
|
77
|
+
β βββ server.ts # π μλ² μ§μ
μ (Entry Point)
|
|
54
78
|
β βββ app.test.ts # π§ͺ Vitest μν ν
μ€νΈ μ½λ
|
|
55
|
-
βββ .env # π νκ²½ λ³μ (μλ μμ±)
|
|
79
|
+
βββ .env # π νκ²½ λ³μ (DATABASE_URL μλ μμ±)
|
|
80
|
+
βββ docker-compose.yml # π³ PostgreSQL 컨ν
μ΄λ μ€μ
|
|
56
81
|
βββ vitest.config.ts # π§ͺ Vitest μ€μ νμΌ
|
|
57
82
|
βββ tsconfig.json # βοΈ TS μ»΄νμΌλ¬ μ€μ (TS μ ν μ)
|
|
58
83
|
βββ package.json # π¦ μμ‘΄μ± λ° μ€ν¬λ¦½νΈ
|
|
59
84
|
```
|
|
60
85
|
|
|
61
|
-
μ£ΌμΈλ, μμ²νμ λλ‘ π Tech Stack (κΈ°μ μ€ν) μΉμ
λΆν° λ§μ§λ§κΉμ§μ λ΄μ©μ λ§ν¬λ€μ΄ μ½λλ‘ μ λ¦¬ν΄ λ립λλ€. μ΄ λΆλΆμ μ£ΌμΈλμ΄ μ΄λ²μ ν΄κ²°νμ κΈ°μ μ λμ κ³Όμ λ€μ΄ κ³ μ€λν λ΄κ²¨ μμ΄ ν¬νΈν΄λ¦¬μ€λ‘μμ κ°μΉκ° λ§€μ° λμ΅λλ€.
|
|
62
|
-
|
|
63
|
-
Markdown
|
|
64
|
-
|
|
65
86
|
## π Tech Stack (κΈ°μ μ€ν)
|
|
66
87
|
|
|
67
88
|
- **Runtime**: Node.js (v20+)
|
|
68
89
|
- **Framework**: Express.js
|
|
69
90
|
- **Language**: JavaScript (ESM) / TypeScript 5.x
|
|
91
|
+
- **ORM**: Prisma (PostgreSQL)
|
|
92
|
+
- **Infrastructure**: Docker Compose
|
|
70
93
|
- **Testing**: Vitest, Supertest
|
|
71
94
|
- **Dev Tools**:
|
|
72
95
|
- `tsx` (TypeScript Execution Engine)
|
|
73
96
|
- `nodemon` (Hot Reload)
|
|
97
|
+
- `@clack/prompts` (Interactive CLI UI)
|
|
74
98
|
- `dotenv` (Environment Variables)
|
|
75
99
|
- `cors` (Cross-Origin Resource Sharing)
|
|
76
100
|
- `chalk` (CLI Styling)
|
|
@@ -78,15 +102,18 @@ Markdown
|
|
|
78
102
|
## π Retrospective
|
|
79
103
|
|
|
80
104
|
- **νμ€νλ νκ²½μ μ€μμ±**: CJSμμ ESMμΌλ‘ λμ΄κ°λ κ³ΌλκΈ°μ λ¬Έμ λ₯Ό ν΄κ²°νλ©° λͺ¨λ μλ°μ€ν¬λ¦½νΈ λͺ¨λ μμ€ν
μ λν κΉμ μ΄ν΄λ₯Ό μ»μμ΅λλ€.
|
|
105
|
+
- **μλ¬ νΈλ€λ§μ μ€μν**: κ°λ³ 컨νΈλ‘€λ¬μμ λ°λ³΅λλ μλ¬ μ²λ¦¬ λ‘μ§μ μ μ λ―Έλ€μ¨μ΄λ‘ μμνμ¬ μ½λ κ°λ
μ±κ³Ό μ μ§λ³΄μμ±μ κ·Ήλννμ΅λλ€.
|
|
106
|
+
- **μΈνλΌ νκ²½ μ΄μ ν΄κ²°**: λ‘컬 PostgreSQLκ³Όμ ν¬νΈ μΆ©λ(5432 vs 5433) λ° λ컀 λ³Όλ₯¨ μΈμ¦ λ¬Έμ λ₯Ό ν΄κ²°νλ©°, μ¬μ©μμκ² κ°μ₯ μμ μ μΈ DB μ°κ²° κ°μ΄λλ₯Ό μ 곡νλ λ° μ±κ³΅νμ΅λλ€.
|
|
81
107
|
- **UX κΈ°λ° μ€κ³**: μ¬μ©μκ° νλ‘μ νΈλ₯Ό μμ±νμλ§μ `npm run dev`μ `npm test`λ₯Ό μ¦μ μ€νν μ μλ "Zero-Config" νκ²½μ μ 곡νλ λ° μ§μ€νμ΅λλ€.
|
|
82
|
-
- **λ°°ν¬ νλ‘μΈμ€μ μ±μ**: μλ λ°°ν¬μ μνμ±μ CI/CDμ OIDC λμ
μ ν΅ν΄ μλννλ©° μννΈμ¨μ΄ λ¦΄λ¦¬μ€ κ³Όμ μ μμ μ±μ ν보νμ΅λλ€.
|
|
83
108
|
|
|
84
109
|
## πΊοΈ Roadmap (Future Plans)
|
|
85
110
|
|
|
86
111
|
- [x] **TypeScript Support**: `.ts` ν
νλ¦Ώ λ° `tsx` νκ²½ μ΅μ ν
|
|
87
112
|
- [x] **Test Environment**: Vitest λ° Supertest μ€μ μλν
|
|
88
|
-
- [
|
|
89
|
-
- [
|
|
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 λ± μ£Όμ νλ«νΌ λ°°ν¬ κ°μ΄λλΌμΈ μΆκ°
|
|
90
117
|
|
|
91
118
|
## π License
|
|
92
119
|
|
package/bin/cli.js
CHANGED
|
@@ -14,11 +14,11 @@ const __dirname = path.dirname(__filename);
|
|
|
14
14
|
async function run() {
|
|
15
15
|
console.clear();
|
|
16
16
|
|
|
17
|
-
// 1. μμ μΈμ¬
|
|
18
|
-
p.intro(`${chalk.bgBlue.white(' create-express-esm ')} ${chalk.dim('v1.
|
|
17
|
+
// 1. μμ μΈμ¬
|
|
18
|
+
p.intro(`${chalk.bgBlue.white(' create-express-esm ')} ${chalk.dim('v1.2.0-beta')}`);
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
// 2. μ¬μ©μ μ§λ¬Έ κ·Έλ£Ή
|
|
21
|
+
// 2. μ¬μ©μ μ§λ¬Έ κ·Έλ£Ή
|
|
22
22
|
const project = await p.group(
|
|
23
23
|
{
|
|
24
24
|
projectName: () =>
|
|
@@ -43,6 +43,11 @@ async function run() {
|
|
|
43
43
|
message: 'Vitest ν
μ€νΈ νκ²½μ μΆκ°νμκ² μ΅λκΉ?',
|
|
44
44
|
initialValue: true,
|
|
45
45
|
}),
|
|
46
|
+
useDb: () =>
|
|
47
|
+
p.confirm({
|
|
48
|
+
message: 'Prisma ORM (PostgreSQL) λ° μ μ μλ¬ νΈλ€λ§μ μΆκ°νμκ² μ΅λκΉ?',
|
|
49
|
+
initialValue: false,
|
|
50
|
+
}),
|
|
46
51
|
},
|
|
47
52
|
{
|
|
48
53
|
onCancel: () => {
|
|
@@ -52,18 +57,19 @@ async function run() {
|
|
|
52
57
|
}
|
|
53
58
|
);
|
|
54
59
|
|
|
55
|
-
const { projectName, language, useTest } = project;
|
|
60
|
+
const { projectName, language, useTest, useDb } = project;
|
|
56
61
|
const targetPath = path.join(process.cwd(), projectName);
|
|
57
62
|
const templatePath = path.join(__dirname, '../template', language);
|
|
63
|
+
const commonPath = path.join(__dirname, '../template/common');
|
|
58
64
|
|
|
59
|
-
// 3. νμΌ κ΅¬μ± μμ
|
|
65
|
+
// 3. νμΌ κ΅¬μ± μμ
|
|
60
66
|
const s = p.spinner();
|
|
61
67
|
s.start('νλ‘μ νΈ ν
νλ¦Ώμ 볡μ¬νλ μ€...');
|
|
62
68
|
|
|
63
|
-
// ν
νλ¦Ώ
|
|
69
|
+
// κΈ°λ³Έ μΈμ΄ ν
νλ¦Ώ 볡μ¬
|
|
64
70
|
await fs.copy(templatePath, targetPath);
|
|
65
71
|
|
|
66
|
-
// λνΈ νμΌ λ³ν (
|
|
72
|
+
// λνΈ νμΌ λ³ν (_env -> .env λ±)
|
|
67
73
|
const renameMap = {
|
|
68
74
|
'gitignore': '.gitignore',
|
|
69
75
|
'_gitignore': '.gitignore',
|
|
@@ -75,50 +81,110 @@ async function run() {
|
|
|
75
81
|
if (await fs.pathExists(oldFilePath)) {
|
|
76
82
|
await fs.move(oldFilePath, path.join(targetPath, newName), { overwrite: true });
|
|
77
83
|
if (newName === '.env') {
|
|
78
|
-
|
|
84
|
+
await fs.copy(path.join(targetPath, '.env'), path.join(targetPath, '.env.example'));
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
// 4.
|
|
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 λμ μ΅μ ν
|
|
84
150
|
const pkgPath = path.join(targetPath, 'package.json');
|
|
85
151
|
const pkg = await fs.readJson(pkgPath);
|
|
86
152
|
pkg.name = projectName;
|
|
87
153
|
|
|
88
|
-
// TypeScript μ ν μ μ€ν νκ²½(tsx) κ°μ μ€μ
|
|
89
154
|
if (language === 'ts') {
|
|
90
155
|
pkg.scripts.dev = "nodemon --exec tsx src/server.ts";
|
|
91
156
|
pkg.devDependencies["tsx"] = "^4.7.0";
|
|
92
|
-
// ꡬν ts-node μ κ±°
|
|
93
|
-
if (pkg.devDependencies["ts-node"]) delete pkg.devDependencies["ts-node"];
|
|
94
157
|
}
|
|
95
158
|
|
|
96
|
-
// ν
μ€νΈ νκ²½
|
|
97
|
-
const configExt = language === 'ts' ? 'ts' : 'js';
|
|
98
|
-
const testFileExt = language === 'ts' ? 'ts' : 'js';
|
|
99
|
-
|
|
159
|
+
// ν
μ€νΈ νκ²½ μ€μ (λΉμ¬μ© μ κ΄λ ¨ νμΌ λ° ν¨ν€μ§ μ κ±°)
|
|
100
160
|
if (!useTest) {
|
|
101
|
-
|
|
161
|
+
const configExt = language === 'ts' ? 'ts' : 'js';
|
|
162
|
+
const testFileExt = language === 'ts' ? 'ts' : 'js';
|
|
102
163
|
await fs.remove(path.join(targetPath, `vitest.config.${configExt}`));
|
|
103
164
|
await fs.remove(path.join(targetPath, `src/app.test.${testFileExt}`));
|
|
104
|
-
|
|
105
|
-
// package.jsonμμ κ΄λ ¨ μ€μ μ κ±°
|
|
106
165
|
delete pkg.scripts.test;
|
|
107
166
|
delete pkg.scripts["test:ui"];
|
|
108
167
|
delete pkg.scripts["test:run"];
|
|
109
168
|
delete pkg.devDependencies.vitest;
|
|
110
169
|
delete pkg.devDependencies.supertest;
|
|
111
170
|
if (pkg.devDependencies["@types/supertest"]) delete pkg.devDependencies["@types/supertest"];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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";
|
|
116
182
|
}
|
|
117
183
|
|
|
118
184
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
119
185
|
s.stop('νμΌ κ΅¬μ± μλ£!');
|
|
120
186
|
|
|
121
|
-
//
|
|
187
|
+
// 6. μμ‘΄μ± μ€μΉ
|
|
122
188
|
const installSpinner = p.spinner();
|
|
123
189
|
installSpinner.start('μμ‘΄μ± ν¨ν€μ§λ₯Ό μ€μΉνλ μ€... (npm install)');
|
|
124
190
|
|
|
@@ -129,12 +195,15 @@ async function run() {
|
|
|
129
195
|
installSpinner.stop(chalk.red('μ€μΉ μ€ν¨ (μλ μ€μΉκ° νμν μ μμ΅λλ€)'));
|
|
130
196
|
}
|
|
131
197
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
'
|
|
136
|
-
|
|
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`;
|
|
137
205
|
|
|
206
|
+
p.note(chalk.cyan(nextSteps), 'μμνλ €λ©΄ λ€μ λͺ
λ Ήμ΄λ₯Ό μ
λ ₯νμΈμ');
|
|
138
207
|
p.outro(chalk.green('β¨ λͺ¨λ μ€λΉκ° λλ¬μ΅λλ€. μ¦κ±°μ΄ κ°λ° λμΈμ!'));
|
|
139
208
|
|
|
140
209
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -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"
|
package/template/ts/_env
CHANGED