create-express-esm 1.2.3 → 1.2.5

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.
Files changed (45) hide show
  1. package/README.md +39 -58
  2. package/bin/cli.js +78 -80
  3. package/package.json +1 -1
  4. package/template/common/docker-compose.yml.mariadb +14 -0
  5. package/template/common/docker-compose.yml.mongodb +15 -0
  6. package/template/common/docker-compose.yml.mysql +14 -0
  7. package/template/common/{docker-compose.yml → docker-compose.yml.postgresql} +1 -1
  8. package/template/common/prisma/schema.prisma.mariadb +14 -0
  9. package/template/common/prisma/schema.prisma.mongodb +14 -0
  10. package/template/common/prisma/schema.prisma.mysql +14 -0
  11. package/template/common/src/js/controllers/userController.js +10 -0
  12. package/template/common/src/js/lib/prisma.js +1 -0
  13. package/template/common/src/js/middlewares/errorMiddleware.js +9 -0
  14. package/template/common/src/js/services/userService.js +7 -0
  15. package/template/common/src/js/utils/appError.js +10 -0
  16. package/template/common/src/ts/controllers/userController.ts +11 -0
  17. package/template/common/src/ts/lib/prisma.ts +1 -0
  18. package/template/common/src/ts/middlewares/errorMiddleware.ts +11 -0
  19. package/template/{ts/src → common/src/ts}/routes/userRoutes.ts +2 -2
  20. package/template/common/src/ts/services/userService.ts +12 -0
  21. package/template/common/src/{utils → ts/utils}/appError.ts +3 -1
  22. package/template/js/_env +0 -3
  23. package/template/js/src/app.js +3 -7
  24. package/template/js/src/server.js +1 -1
  25. package/template/ts/_env +0 -3
  26. package/template/ts/src/server.ts +1 -1
  27. package/template/common/src/controllers/userController.js +0 -0
  28. package/template/common/src/controllers/userController.ts +0 -42
  29. package/template/common/src/lib/prisma.js +0 -0
  30. package/template/common/src/lib/prisma.ts +0 -0
  31. package/template/common/src/middlewares/errorMiddleware.ts +0 -19
  32. package/template/common/src/routes/userRoutes.js +0 -0
  33. package/template/common/src/routes/userRoutes.ts +0 -0
  34. package/template/common/src/services/userService.js +0 -0
  35. package/template/common/src/services/userService.ts +0 -0
  36. package/template/js/package-lock.json +0 -3366
  37. package/template/js/src/controllers/userController.js +0 -10
  38. package/template/js/src/services/userService.js +0 -8
  39. package/template/ts/package-lock.json +0 -4020
  40. package/template/ts/src/controllers/userController.ts +0 -17
  41. package/template/ts/src/services/userService.ts +0 -14
  42. /package/template/common/prisma/{schema.prisma → schema.prisma.postgresql} +0 -0
  43. /package/template/{js/src → common/src/js}/app.test.js +0 -0
  44. /package/template/{js/src → common/src/js}/routes/userRoutes.js +0 -0
  45. /package/template/{ts/src → common/src/ts}/app.test.ts +0 -0
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # 🚀 Create Express ESM (CLI)
2
2
 
3
- > **Modern Express Generator with Database & Error Handling**
3
+ > **Modern Express Generator with Multi-Database & Zero-Config**
4
4
  >
5
- > "1초 만에 완성하는 Modern Express(ESM) + Prisma + Docker 환경"
5
+ > "1초 만에 완성하는 Modern Express(ESM) + Prisma + Multi-DB Docker 환경"
6
6
  >
7
- > CommonJS(require)가 아닌, 최신 ES Modules(import/export) 문법을 기반으로 하며, 데이터베이스 연동 전문적인 에러 핸들링까지 포함된 Express 프로젝트 구조를 자동으로 생성해주는 CLI 도구입니다.
7
+ > 최신 ES Modules(import/export) 기반으로, 사용자가 선택한 데이터베이스(PostgreSQL, MySQL, MariaDB, MongoDB) 환경을 즉시 구축해주는 지능형 CLI 도구입니다.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/create-express-esm.svg)](https://www.npmjs.com/package/create-express-esm)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -15,19 +15,19 @@
15
15
 
16
16
  ## ✨ Key Features (핵심 기능)
17
17
 
18
- 기존 `express-generator`의 한계를 분석하고 개선하여 개발했습니다. v1.2.0 업데이트를 통해 실무형 풀스택 베이스를 제공합니다.
18
+ v1.2.4 업데이트를 통해 더욱 강력해진 실무형 풀스택 베이스를 제공합니다.
19
19
 
20
- - **🌐 Multi-Language Support**: JavaScript(ESM)와 **TypeScript**원하는 개발 환경을 선택할 수 있습니다.
21
- - **🗄️ Database Integration**: **Prisma ORM**과 **PostgreSQL** 환경을 즉시 구축합니다. (Docker Compose 자동 생성)
22
- - **🚨 Professional Error Handling**: `AppError` 클래스와 **Global Error Middleware**를 통한 중앙 집중식 에러 처리 시스템을 제공합니다.
23
- - **🧪 Integrated Testing**: 최신 테스트 프레임워크인 **Vitest**와 API 테스트용 **Supertest** 환경을 자동 설정합니다.
20
+ - **🗄️ Multi-Database Support**: **PostgreSQL, MySQL, MariaDB, MongoDB** 중 프로젝트에 필요한 DB를 선택할 수 있습니다.
21
+ - **🐳 Smart Docker Orchestration**: DB 엔진에 최적화된 Docker Compose 설정을 자동 생성합니다. (포트 충돌 방지 설계 적용)
24
22
  - **🏗 Layered Architecture**: 실무 표준인 `Controller` - `Service` - `Route` - `Middleware` 계층 구조를 제공합니다.
25
- - **⚡️ Modern Execution**: `ts-node`의 ESM 호환성 문제를 해결한 **`tsx`**를 채택하여 쾌적한 개발 환경을 제공합니다.
26
- - **📦 Smart Auto-Installation**: 프로젝트 생성 즉시 의존성 설치 환경 변수(`.env`) 세팅을 완료합니다.
23
+ - **🚨 Professional Error Handling**: `AppError` 클래스와 **Global Error Middleware**를 통한 중앙 집중식 에러 관리 시스템을 내장하고 있습니다.
24
+ - **🧪 Integrated Testing**: 최신 테스트 프레임워크인 **Vitest**와 API 테스트용 **Supertest** 환경을 자동 설정합니다.
25
+ - **⚡ Modern Execution**: `ts-node`보다 빠르고 ESM 호환성이 뛰어난 **`tsx`**를 채택했습니다.
26
+ - **📦 Zero-Config Setup**: 프로젝트 생성 즉시 의존성 설치, `.env` 환경 변수 세팅 및 Prisma 스키마 최적화를 완료합니다.
27
27
 
28
28
  ## 🚀 Quick Start (사용법)
29
29
 
30
- 별도의 설치 없이 `npx` 명령어로 즉시 실행할 있습니다.
30
+ 별도의 설치 없이 `npx` 명령어로 즉시 실행하여 인터렉티브하게 프로젝트를 생성하세요.
31
31
 
32
32
  ```bash
33
33
  npx create-express-esm
@@ -35,37 +35,38 @@ npx create-express-esm
35
35
 
36
36
  ### 프로젝트 생성 후 DB 시작하기
37
37
 
38
- DB 옵션을 선택했다면, 단 몇 줄의 명령어로 개발 준비가 끝납니다.
39
-
40
- ```bash
38
+ ```
41
39
  # 1. 프로젝트 폴더 이동
42
40
  cd my-app
43
41
 
44
- # 2. Docker를 통한 PostgreSQL 실행
42
+ # 2. Docker를 통한 데이터베이스 실행
45
43
  npm run db:up
46
44
 
47
- # 3. Prisma 스키마를 DB에 반영 (테이블 생성)
45
+ # 3. Prisma 스키마 반영 (테이블/컬렉션 생성)
48
46
  npm run db:push
49
47
 
50
- # 4. 서버 실행
48
+ # 4. 개발 서버 실행
51
49
  npm run dev
52
50
  ```
53
51
 
54
- 또는 전역으로 설치하여 사용할 수도 있습니다.
52
+ ## 🗄️ Database Options
55
53
 
56
- ```bash
57
- npm install -g create-express-esm
58
- create-express-esm
59
- ```
54
+ v1.2.4에서는 각 환경별 최적화된 기본 포트를 제공하여 윈도우 시스템(Hyper-V)과의 포트 충돌을 최소화하고, 선택한 DB에 맞는 전용 환경을 구축합니다.
55
+
56
+ | Database | Docker Image | Default Port | Prisma Provider |
57
+ | :------------- | :------------------- | :----------- | :-------------- |
58
+ | **PostgreSQL** | `postgres:15-alpine` | `5433` | `postgresql` |
59
+ | **MySQL** | `mysql:8.0` | `4306` | `mysql` |
60
+ | **MariaDB** | `mariadb:10.11` | `5306` | `mysql` |
61
+ | **MongoDB** | `mongo:6.0` | `27017` | `mongodb` |
60
62
 
61
63
  ## 📂 Project Structure (폴더 구조)
62
64
 
63
- 이 도구는 **Layered Architecture (계층형 아키텍처)**를 기반으로 프로젝트를 생성합니다.
64
- 관심사 분리(Separation of Concerns) 원칙을 적용하여 유지보수가 쉬운 구조를 제공합니다.
65
+ 이 도구는 **Layered Architecture (계층형 아키텍처)**를 기반으로 프로젝트를 생성하여 높은 유지보수성을 보장합니다.
65
66
 
66
67
  ```text
67
68
  my-app/
68
- ├── prisma/ # 🗄️ Prisma Schema & Migrations
69
+ ├── prisma/ # 🗄️ Prisma Schema (선택한 DB 전용 템플릿)
69
70
  ├── src/
70
71
  │ ├── controllers/ # 🕹️ 요청 처리 및 응답 반환 (Controller Layer)
71
72
  │ ├── services/ # 🧠 비즈니스 로직 처리 (Service Layer)
@@ -74,46 +75,26 @@ my-app/
74
75
  │ ├── utils/ # 🛠️ AppError 클래스 등 공통 유틸리티
75
76
  │ ├── lib/ # 🖇️ Prisma Client 인스턴스 (Singleton)
76
77
  │ ├── app.ts # 🏗️ Express 앱 설정 및 미들웨어
77
- ├── server.ts # 🚀 서버 진입점 (Entry Point)
78
- │ └── app.test.ts # 🧪 Vitest 샘플 테스트 코드
78
+ └── server.ts # 🚀 서버 진입점 (Entry Point)
79
79
  ├── .env # 🔐 환경 변수 (DATABASE_URL 자동 생성)
80
- ├── docker-compose.yml # 🐳 PostgreSQL 컨테이너 설정
80
+ ├── docker-compose.yml # 🐳 DB 컨테이너 설정 (Port conflict 방지 설계)
81
81
  ├── vitest.config.ts # 🧪 Vitest 설정 파일
82
- ├── tsconfig.json # ⚙️ TS 컴파일러 설정 (TS 선택 시)
83
82
  └── package.json # 📦 의존성 및 스크립트
84
83
  ```
85
84
 
86
- ## 🛠 Tech Stack (기술 스택)
87
-
88
- - **Runtime**: Node.js (v20+)
89
- - **Framework**: Express.js
90
- - **Language**: JavaScript (ESM) / TypeScript 5.x
91
- - **ORM**: Prisma (PostgreSQL)
92
- - **Infrastructure**: Docker Compose
93
- - **Testing**: Vitest, Supertest
94
- - **Dev Tools**:
95
- - `tsx` (TypeScript Execution Engine)
96
- - `nodemon` (Hot Reload)
97
- - `@clack/prompts` (Interactive CLI UI)
98
- - `dotenv` (Environment Variables)
99
- - `cors` (Cross-Origin Resource Sharing)
100
- - `chalk` (CLI Styling)
101
-
102
- ## 📝 Retrospective
103
-
104
- - **표준화된 환경의 중요성**: CJS에서 ESM으로 넘어가는 과도기적 문제를 해결하며 모던 자바스크립트 모듈 시스템에 대한 깊은 이해를 얻었습니다.
105
- - **에러 핸들링의 중앙화**: 개별 컨트롤러에서 반복되던 에러 처리 로직을 전역 미들웨어로 위임하여 코드 가독성과 유지보수성을 극대화했습니다.
106
- - **인프라 환경 이슈 해결**: 로컬 PostgreSQL과의 포트 충돌(5432 vs 5433) 및 도커 볼륨 인증 문제를 해결하며, 사용자에게 가장 안정적인 DB 연결 가이드를 제공하는 데 성공했습니다.
107
- - **UX 기반 설계**: 사용자가 프로젝트를 생성하자마자 `npm run dev`와 `npm test`를 즉시 실행할 수 있는 "Zero-Config" 환경을 제공하는 데 집중했습니다.
108
-
109
85
  ## 🗺️ Roadmap (Future Plans)
110
86
 
111
- - [x] **TypeScript Support**: `.ts` 템플릿 `tsx` 환경 최적화
112
- - [x] **Test Environment**: Vitest 및 Supertest 설정 자동화
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 등 주요 플랫폼 배포 가이드라인 추가
87
+ - [x] TypeScript & tsx: 최신 TS 환경 완벽 지원
88
+
89
+ - [x] Testing Setup: Vitest/Supertest 자동화
90
+
91
+ - [x] Multi-DB Support: 4종 DB(Postgres, MySQL, MariaDB, Mongo) 지원
92
+
93
+ - [ ] Authentication Template: JWT/Passport 기반 인증 로직 추가
94
+
95
+ - [ ] Validation Layer: Zod를 이용한 요청 값 검증 미들웨어 추가
96
+
97
+ - [ ] API Documentation: Swagger(OpenAPI) 자동 생성 지원
117
98
 
118
99
  ## 📝 License
119
100
 
package/bin/cli.js CHANGED
@@ -14,8 +14,8 @@ 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.2.0-beta')}`);
17
+ // 1. 시작 인사 (v1.2.4 버전 반영)
18
+ p.intro(`${chalk.bgBlue.white(' create-express-esm ')} ${chalk.dim('v1.2.4')}`);
19
19
 
20
20
  try {
21
21
  // 2. 사용자 질문 그룹
@@ -45,9 +45,21 @@ async function run() {
45
45
  }),
46
46
  useDb: () =>
47
47
  p.confirm({
48
- message: 'Prisma ORM (PostgreSQL) 전역 에러 핸들링을 추가하시겠습니까?',
48
+ message: 'Prisma ORM 및 데이터베이스 환경을 추가하시겠습니까?',
49
49
  initialValue: false,
50
50
  }),
51
+ dbType: ({ results }) => {
52
+ if (!results.useDb) return;
53
+ return p.select({
54
+ message: '사용할 데이터베이스를 선택하세요:',
55
+ options: [
56
+ { value: 'postgresql', label: 'PostgreSQL' },
57
+ { value: 'mysql', label: 'MySQL' },
58
+ { value: 'mariadb', label: 'MariaDB' },
59
+ { value: 'mongodb', label: 'MongoDB (NoSQL)' },
60
+ ],
61
+ });
62
+ },
51
63
  },
52
64
  {
53
65
  onCancel: () => {
@@ -57,93 +69,89 @@ async function run() {
57
69
  }
58
70
  );
59
71
 
60
- const { projectName, language, useTest, useDb } = project;
72
+ const { projectName, language, useTest, useDb, dbType } = project;
61
73
  const targetPath = path.join(process.cwd(), projectName);
62
74
  const templatePath = path.join(__dirname, '../template', language);
63
75
  const commonPath = path.join(__dirname, '../template/common');
64
76
 
65
77
  // 3. 파일 구성 시작
66
78
  const s = p.spinner();
67
- s.start('프로젝트 템플릿을 복사하는 중...');
79
+ s.start('프로젝트 템플릿을 생성하는 중...');
68
80
 
69
- // 기본 언어 템플릿 복사
81
+ // (1) 기본 언어 템플릿 복사 (js 또는 ts)
70
82
  await fs.copy(templatePath, targetPath);
71
83
 
72
- // 도트 파일 변환 (_env -> .env 등)
73
- const renameMap = {
74
- 'gitignore': '.gitignore',
75
- '_gitignore': '.gitignore',
76
- '_env': '.env'
77
- };
78
-
79
- for (const [oldName, newName] of Object.entries(renameMap)) {
80
- const oldFilePath = path.join(targetPath, oldName);
81
- if (await fs.pathExists(oldFilePath)) {
82
- await fs.move(oldFilePath, path.join(targetPath, newName), { overwrite: true });
84
+ // (2) 도트 파일 변환 (_env -> .env 등)
85
+ const dotFiles = ['gitignore', '_gitignore', '_env'];
86
+ for (const f of dotFiles) {
87
+ const oldPath = path.join(targetPath, f);
88
+ if (await fs.pathExists(oldPath)) {
89
+ const newName = f.startsWith('_') ? f.replace('_', '.') : `.${f}`;
90
+ await fs.move(oldPath, path.join(targetPath, newName), { overwrite: true });
91
+ // .env 파일 생성 .env.example도 같이 생성
83
92
  if (newName === '.env') {
84
93
  await fs.copy(path.join(targetPath, '.env'), path.join(targetPath, '.env.example'));
85
94
  }
86
95
  }
87
96
  }
88
97
 
89
- // 4. DB 및 에러 핸들링 선택 시 추가 파일 복사 코드 주입
98
+ // 4. DB 선택 시 기능 병합 (Common 소스 복사)
90
99
  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
- }
100
+ // (1) Prisma & Docker 설정 복사
101
+ await fs.ensureDir(path.join(targetPath, 'prisma'));
102
+ await fs.copy(
103
+ path.join(commonPath, 'prisma', `schema.prisma.${dbType}`),
104
+ path.join(targetPath, 'prisma', 'schema.prisma')
105
+ );
106
+ await fs.copy(
107
+ path.join(commonPath, `docker-compose.yml.${dbType}`),
108
+ path.join(targetPath, 'docker-compose.yml')
109
+ );
110
+
111
+ // (2) ⭐ [핵심 수정] 언어별 Common 소스 코드 병합
112
+ // template/common/src/js (또는 ts) 폴더 내부를 target/src로 병합 복사
113
+ const commonSrcPath = path.join(commonPath, 'src', language);
114
+ if (await fs.pathExists(commonSrcPath)) {
115
+ await fs.copy(commonSrcPath, path.join(targetPath, 'src'), {
116
+ overwrite: true,
117
+ errorOnExist: false
118
+ });
111
119
  }
112
120
 
113
- // (3) app.ts / app.js 에 코드 주입 (중요!)
121
+ // (3) app.ts / app.js 에 코드 주입
114
122
  const mainFilePath = path.join(targetPath, `src/app.${language}`);
115
123
  if (await fs.pathExists(mainFilePath)) {
116
124
  let content = await fs.readFile(mainFilePath, 'utf-8');
117
125
 
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}`;
126
+ // 중복 주입 방지 체크
127
+ if (!content.includes('userRoutes')) {
128
+ const imports = [
129
+ `import userRoutes from './routes/userRoutes.js';`,
130
+ `import { errorHandler } from './middlewares/errorMiddleware.js';`
131
+ ].join('\n');
132
+
133
+ content = imports + '\n' + content;
134
+ // express.json() 다음에 라우터 연결
135
+ content = content.replace(/app\.use\(express\.json\(\)\);?/g, (match) => {
136
+ return `${match}\napp.use('/api/users', userRoutes);`;
137
+ });
138
+ // export default 앞에 에러 핸들러 연결
139
+ const errorMiddlewareCode = `\n// 전역 에러 핸들러\napp.use(errorHandler);\n`;
140
+ content = content.replace(/export default app;?/g, `${errorMiddlewareCode}\nexport default app;`);
141
+
142
+ await fs.writeFile(mainFilePath, content);
135
143
  }
136
-
137
- await fs.writeFile(mainFilePath, content);
138
144
  }
139
145
 
140
- // (4) .env 파일에 DATABASE_URL 추가
146
+ // (4) .env 파일에 DB 타입별 DATABASE_URL 추가
141
147
  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);
148
+ const dbUrls = {
149
+ postgresql: 'postgresql://myuser:mypassword@localhost:5433/mydb?schema=public',
150
+ mysql: 'mysql://root:mypassword@localhost:4306/mydb',
151
+ mariadb: 'mysql://root:mypassword@localhost:5306/mydb',
152
+ mongodb: 'mongodb://root:mypassword@localhost:27017/mydb?authSource=admin'
153
+ };
154
+ await fs.appendFile(envPath, `\n# Database Connection\nDATABASE_URL="${dbUrls[dbType]}"\n`);
147
155
  }
148
156
 
149
157
  // 5. package.json 동적 최적화
@@ -156,50 +164,40 @@ DATABASE_URL="postgresql://myuser:mypassword@localhost:5432/mydb?schema=public"
156
164
  pkg.devDependencies["tsx"] = "^4.7.0";
157
165
  }
158
166
 
159
- // 테스트 환경 설정 (비사용 시 관련 파일 및 패키지 제거)
160
167
  if (!useTest) {
161
168
  const configExt = language === 'ts' ? 'ts' : 'js';
162
- const testFileExt = language === 'ts' ? 'ts' : 'js';
163
169
  await fs.remove(path.join(targetPath, `vitest.config.${configExt}`));
164
- await fs.remove(path.join(targetPath, `src/app.test.${testFileExt}`));
170
+ await fs.remove(path.join(targetPath, `src/app.test.${configExt}`));
165
171
  delete pkg.scripts.test;
166
- delete pkg.scripts["test:ui"];
167
- delete pkg.scripts["test:run"];
168
172
  delete pkg.devDependencies.vitest;
169
173
  delete pkg.devDependencies.supertest;
170
- if (pkg.devDependencies["@types/supertest"]) delete pkg.devDependencies["@types/supertest"];
171
174
  }
172
175
 
173
- // DB 의존성 및 스크립트 추가
174
176
  if (useDb) {
175
177
  pkg.scripts["db:up"] = "docker-compose up -d";
176
178
  pkg.scripts["db:push"] = "prisma db push";
177
- pkg.scripts["prisma:generate"] = "prisma generate";
178
- pkg.scripts["prisma:studio"] = "prisma studio";
179
-
180
179
  pkg.dependencies["@prisma/client"] = "^5.0.0";
181
180
  pkg.devDependencies["prisma"] = "^5.0.0";
182
181
  }
183
182
 
184
183
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
185
- s.stop('파일 구성 완료!');
184
+ s.stop('프로젝트 파일 구성 완료!');
186
185
 
187
186
  // 6. 의존성 설치
188
187
  const installSpinner = p.spinner();
189
188
  installSpinner.start('의존성 패키지를 설치하는 중... (npm install)');
190
-
191
189
  try {
192
190
  execSync('npm install', { cwd: targetPath, stdio: 'ignore' });
193
191
  installSpinner.stop('설치 완료!');
194
192
  } catch (e) {
195
- installSpinner.stop(chalk.red('설치 실패 (수동 설치가 필요할 있습니다)'));
193
+ installSpinner.stop(chalk.red('설치 실패 (수동으로 npm install을 실행해 주세요)'));
196
194
  }
197
195
 
198
- // 7. 마무리
196
+ // 7. 마무리 안내
199
197
  let nextSteps = `cd ${projectName}\n`;
200
198
  if (useDb) {
201
- nextSteps += `${chalk.bold('npm run db:up')} (DB 실행)\n`;
202
- nextSteps += `${chalk.bold('npm run db:push')} (테이블 생성)\n`;
199
+ nextSteps += `${chalk.bold('npm run db:up')} (Docker 실행)\n`;
200
+ nextSteps += `${chalk.bold('npm run db:push')} (DB 스키마 반영)\n`;
203
201
  }
204
202
  nextSteps += `npm run dev`;
205
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-express-esm",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
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",
@@ -0,0 +1,14 @@
1
+ services:
2
+ db:
3
+ image: mariadb:10.11
4
+ restart: always
5
+ ports:
6
+ - "5306:3306"
7
+ environment:
8
+ MARIADB_ROOT_PASSWORD: ${DB_PASSWORD:-mypassword}
9
+ MARIADB_DATABASE: ${DB_NAME:-mydb}
10
+ volumes:
11
+ - mariadb_data:/var/lib/mysql
12
+
13
+ volumes:
14
+ mariadb_data:
@@ -0,0 +1,15 @@
1
+ services:
2
+ db:
3
+ image: mongo:6.0
4
+ restart: always
5
+ ports:
6
+ - "27017:27017"
7
+ environment:
8
+ MONGO_INITDB_ROOT_USERNAME: ${DB_USER:-root}
9
+ MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD:-mypassword}
10
+ MONGO_INITDB_DATABASE: ${DB_NAME:-mydb}
11
+ volumes:
12
+ - mongo_data:/data/db
13
+
14
+ volumes:
15
+ mongo_data:
@@ -0,0 +1,14 @@
1
+ services:
2
+ db:
3
+ image: mysql:8.0
4
+ restart: always
5
+ ports:
6
+ - "4306:3306"
7
+ environment:
8
+ MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-mypassword}
9
+ MYSQL_DATABASE: ${DB_NAME:-mydb}
10
+ volumes:
11
+ - mysql_data:/var/lib/mysql
12
+
13
+ volumes:
14
+ mysql_data:
@@ -8,7 +8,7 @@ services:
8
8
  - POSTGRES_PASSWORD=mypassword
9
9
  - POSTGRES_DB=mydb
10
10
  ports:
11
- - '5432:5432'
11
+ - '5433:5432'
12
12
  volumes:
13
13
  - db-data:/var/lib/postgresql/data
14
14
 
@@ -0,0 +1,14 @@
1
+ datasource db {
2
+ provider = "mysql"
3
+ url = env("DATABASE_URL")
4
+ }
5
+
6
+ generator client {
7
+ provider = "prisma-client-js"
8
+ }
9
+
10
+ model User {
11
+ id Int @id @default(autoincrement())
12
+ email String @unique
13
+ name String?
14
+ }
@@ -0,0 +1,14 @@
1
+ datasource db {
2
+ provider = "mongodb"
3
+ url = env("DATABASE_URL")
4
+ }
5
+
6
+ generator client {
7
+ provider = "prisma-client-js"
8
+ }
9
+
10
+ model User {
11
+ id String @id @default(auto()) @map("_id") @db.ObjectId
12
+ email String @unique
13
+ name String?
14
+ }
@@ -0,0 +1,14 @@
1
+ datasource db {
2
+ provider = "mysql"
3
+ url = env("DATABASE_URL")
4
+ }
5
+
6
+ generator client {
7
+ provider = "prisma-client-js"
8
+ }
9
+
10
+ model User {
11
+ id Int @id @default(autoincrement())
12
+ email String @unique
13
+ name String?
14
+ }
@@ -0,0 +1,10 @@
1
+ import * as userService from '../services/userService.js';
2
+
3
+ export const getUsers = async (req, res, next) => {
4
+ try {
5
+ const users = await userService.findAllUsers();
6
+ res.status(200).json(users);
7
+ } catch (error) {
8
+ next(error); // 에러 미들웨어로 전달
9
+ }
10
+ };
@@ -0,0 +1 @@
1
+ import { PrismaClient } from '@prisma/client'; export const prisma = new PrismaClient();
@@ -0,0 +1,9 @@
1
+ export const errorHandler = (err, req, res, next) => {
2
+ err.statusCode = err.statusCode || 500;
3
+ err.status = err.status || 'error';
4
+
5
+ res.status(err.statusCode).json({
6
+ status: err.status,
7
+ message: err.message,
8
+ });
9
+ };
@@ -0,0 +1,7 @@
1
+ export const findAllUsers = async () => {
2
+ // 실제 DB 연동 전 샘플 데이터
3
+ return [
4
+ { id: 1, name: "Developer", role: "Fullstack" },
5
+ { id: 2, name: "Gemius AI", role: "Assistant" }
6
+ ];
7
+ };
@@ -0,0 +1,10 @@
1
+ export class AppError extends Error {
2
+ constructor(message, statusCode) {
3
+ super(message);
4
+ this.statusCode = statusCode;
5
+ this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
6
+ this.isOperational = true;
7
+
8
+ Error.captureStackTrace(this, this.constructor);
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import * as userService from '../services/userService.js';
3
+
4
+ export const getUsers = async (req: Request, res: Response, next: NextFunction) => {
5
+ try {
6
+ const users = await userService.findAllUsers();
7
+ res.status(200).json(users);
8
+ } catch (error) {
9
+ next(error);
10
+ }
11
+ };
@@ -0,0 +1 @@
1
+ import { PrismaClient } from '@prisma/client'; export const prisma: PrismaClient = new PrismaClient();
@@ -0,0 +1,11 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+
3
+ export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
4
+ const statusCode = err.statusCode || 500;
5
+ const status = err.status || 'error';
6
+
7
+ res.status(statusCode).json({
8
+ status,
9
+ message: err.message,
10
+ });
11
+ };
@@ -1,7 +1,7 @@
1
- import { Router } from 'express';
1
+ import express from 'express';
2
2
  import * as userController from '../controllers/userController.js';
3
3
 
4
- const router = Router();
4
+ const router = express.Router();
5
5
 
6
6
  router.get('/', userController.getUsers);
7
7
 
@@ -0,0 +1,12 @@
1
+ interface User {
2
+ id: number;
3
+ name: string;
4
+ role: string;
5
+ }
6
+
7
+ export const findAllUsers = async (): Promise<User[]> => {
8
+ return [
9
+ { id: 1, name: "Developer", role: "Fullstack" },
10
+ { id: 2, name: "Genius AI", role: "Assistant" }
11
+ ];
12
+ };
@@ -1,11 +1,13 @@
1
1
  export class AppError extends Error {
2
2
  public statusCode: number;
3
+ public status: string;
3
4
  public isOperational: boolean;
4
5
 
5
6
  constructor(message: string, statusCode: number) {
6
7
  super(message);
7
8
  this.statusCode = statusCode;
8
- this.isOperational = true; // 예측 가능한 에러임을 표시
9
+ this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
10
+ this.isOperational = true;
9
11
 
10
12
  Error.captureStackTrace(this, this.constructor);
11
13
  }
package/template/js/_env CHANGED
@@ -1,3 +0,0 @@
1
- # PostgreSQL Connection String
2
- # 형식: postgresql://사용자:비밀번호@호스트:포트/데이터베이스명?schema=public
3
- DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/myapp?schema=public"