create-xpressforge 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,46 +1,137 @@
1
1
  # create-xpressforge
2
2
 
3
- > Production-ready Node.js + Express project scaffolder
3
+ > Scaffold a production-ready Node.js + Express project in seconds — with your choice of architecture, database, auth, and language.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/create-xpressforge)](https://npmjs.com/package/create-xpressforge)
6
6
  [![license](https://img.shields.io/npm/l/create-xpressforge)](LICENSE)
7
+ [![tests](https://img.shields.io/badge/tests-73%20passed-brightgreen)](#)
7
8
 
8
- ## Usage
9
+ ---
10
+
11
+ ## Quick start
9
12
 
10
13
  ```bash
11
14
  npx create-xpressforge my-app
12
15
  ```
13
16
 
14
- Or install globally:
17
+ That's it. Follow the prompts, then:
15
18
 
16
19
  ```bash
17
- npm install -g create-xpressforge
18
- create-xpressforge my-app
20
+ cd my-app
21
+ npm install
22
+ cp .env.example .env # add your DB URI and secrets
23
+ npm run dev
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Interactive prompts
29
+
30
+ ```
31
+ ? Project name: my-app
32
+ ? Project structure: MVC / Modular / Layered
33
+ ? Database: MongoDB / PostgreSQL / MySQL / None
34
+ ? Authentication: JWT / Session / None
35
+ ? Extra features: Rate limiting, Helmet, CORS, Morgan, Validation, Multer, Socket.io, Swagger
36
+ ? Language: JavaScript / TypeScript
37
+ ```
38
+
39
+ ---
40
+
41
+ ## What gets generated
42
+
43
+ Running `npx create-xpressforge my-app` with **MVC + MongoDB + JWT** produces:
44
+
45
+ ```
46
+ my-app/
47
+ ├── src/
48
+ │ ├── config/
49
+ │ │ ├── db.js # MongoDB connection with retry logic
50
+ │ │ └── env.js # Centralised env config
51
+ │ ├── controllers/
52
+ │ │ ├── authController.js # register, login, refresh, getMe
53
+ │ │ └── userController.js # full CRUD with pagination
54
+ │ ├── middlewares/
55
+ │ │ ├── authenticate.js # JWT verify + role-based authorize()
56
+ │ │ ├── errorHandler.js # global error handler
57
+ │ │ └── notFound.js
58
+ │ ├── models/
59
+ │ │ └── User.js # Mongoose schema with indexes
60
+ │ ├── routes/
61
+ │ │ ├── index.js
62
+ │ │ ├── authRoutes.js
63
+ │ │ └── userRoutes.js
64
+ │ ├── services/
65
+ │ │ └── userService.js
66
+ │ └── utils/
67
+ │ ├── apiResponse.js # sendSuccess / sendError / sendPaginated
68
+ │ └── logger.js # coloured console logger
69
+ ├── .env # pre-filled with your choices
70
+ ├── .env.example # safe to commit
71
+ ├── .gitignore
72
+ ├── package.json
73
+ ├── README.md # auto-generated for your stack
74
+ └── server.js
75
+ ```
76
+
77
+ ---
78
+
79
+ ## API endpoints (out of the box)
80
+
81
+ | Method | Endpoint | Description | Auth |
82
+ | -------- | ----------------------- | -------------------- | -------- |
83
+ | `POST` | `/api/v1/auth/register` | Register a new user | — |
84
+ | `POST` | `/api/v1/auth/login` | Login, get tokens | — |
85
+ | `POST` | `/api/v1/auth/refresh` | Refresh access token | — |
86
+ | `GET` | `/api/v1/auth/me` | Get current user | ✅ |
87
+ | `GET` | `/api/v1/users` | Paginated user list | ✅ |
88
+ | `GET` | `/api/v1/users/:id` | Get user by ID | ✅ |
89
+ | `PUT` | `/api/v1/users/:id` | Update user | ✅ |
90
+ | `DELETE` | `/api/v1/users/:id` | Delete user | ✅ Admin |
91
+ | `GET` | `/health` | Health check | — |
92
+
93
+ ---
94
+
95
+ ## Consistent API response format
96
+
97
+ Every endpoint returns the same shape — no surprises:
98
+
99
+ ```json
100
+ // success
101
+ { "success": true, "message": "Login successful", "data": { ... } }
102
+
103
+ // error
104
+ { "success": false, "message": "Invalid email or password" }
105
+
106
+ // paginated
107
+ { "success": true, "data": [...], "pagination": { "total": 100, "page": 2, "limit": 10, "totalPages": 10 } }
19
108
  ```
20
109
 
21
- ## What you get
110
+ ---
22
111
 
23
- Interactive prompts let you choose:
112
+ ## Available scripts
24
113
 
25
- - **Structure** — MVC, Modular (feature-based), or Layered (controller/service/repository)
26
- - **Database** MongoDB (Mongoose), PostgreSQL or MySQL (Prisma), or none
27
- - **Auth** — JWT with refresh tokens, Session, or none
28
- - **Extras** Rate limiting, Helmet, CORS, Morgan, Validation, Multer, Socket.io, Swagger
29
- - **Language** — JavaScript (ES Modules) or TypeScript
114
+ ```bash
115
+ npm run dev # nodemon hot-reload (JS) / tsx watch (TS)
116
+ npm start # production
117
+ npm run build # compile TypeScript (TS only)
118
+ npm test # vitest
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Global install
30
124
 
31
- Every generated project includes:
125
+ ```bash
126
+ npm install -g create-xpressforge
127
+ create-xpressforge my-app
128
+ ```
32
129
 
33
- - Global error handler with Mongoose/Prisma/JWT error detection
34
- - Consistent `apiResponse` helper (`sendSuccess`, `sendError`, `sendPaginated`)
35
- - Custom logger utility
36
- - 404 not-found middleware
37
- - Working User CRUD example
38
- - `.env` + `.env.example` with all variables listed
39
- - Auto-generated README with your stack details
130
+ ---
40
131
 
41
132
  ## Author
42
133
 
43
- Hammad Sadi <hammad.sadi@yahoo.com>
134
+ **Hammad Sadi** · [hammad.sadi@yahoo.com](mailto:hammad.sadi@yahoo.com)
44
135
 
45
136
  ## License
46
137
 
package/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { run } from '../src/index.js';
3
+ import { run } from "../src/index.js";
4
4
 
5
5
  run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-xpressforge",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Production-ready Node.js + Express project scaffolder — MVC, Modular, Layered structures with DB, Auth & more",
5
5
  "author": "Hammad Sadi <hammad.sadi@yahoo.com>",
6
6
  "license": "MIT",
@@ -8,6 +8,7 @@
8
8
  "create-xpressforge": "./bin/cli.js"
9
9
  },
10
10
  "main": "./src/index.js",
11
+ "type": "module",
11
12
  "keywords": [
12
13
  "express",
13
14
  "nodejs",
@@ -26,8 +27,14 @@
26
27
  "bin",
27
28
  "src"
28
29
  ],
30
+ "preferGlobal": true,
29
31
  "engines": {
30
- "node": ">=16.0.0"
32
+ "node": ">=18.0.0"
33
+ },
34
+ "scripts": {
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "test:coverage": "vitest run --coverage"
31
38
  },
32
39
  "dependencies": {
33
40
  "@inquirer/prompts": "^5.0.0",
@@ -36,6 +43,10 @@
36
43
  "fs-extra": "^11.2.0",
37
44
  "gradient-string": "^2.0.2"
38
45
  },
46
+ "devDependencies": {
47
+ "vitest": "^1.6.0",
48
+ "@vitest/coverage-v8": "^1.6.0"
49
+ },
39
50
  "repository": {
40
51
  "type": "git",
41
52
  "url": "https://github.com/hammadsadi/create-xpressforge"
@@ -3,17 +3,15 @@ export function generateAppJs(answers) {
3
3
 
4
4
  const imports = [
5
5
  `import express from 'express';`,
6
- extras.includes('cors') ? `import cors from 'cors';` : '',
7
- extras.includes('helmet') ? `import helmet from 'helmet';` : '',
8
- extras.includes('morgan') ? `import morgan from 'morgan';` : '',
6
+ extras.includes('cors') ? `import cors from 'cors';` : '',
7
+ extras.includes('helmet') ? `import helmet from 'helmet';` : '',
8
+ extras.includes('morgan') ? `import morgan from 'morgan';` : '',
9
9
  extras.includes('rateLimit') ? `import rateLimit from 'express-rate-limit';` : '',
10
10
  extras.includes('swagger') ? `import swaggerUi from 'swagger-ui-express';\nimport { swaggerSpec } from './config/swagger.js';` : '',
11
11
  database !== 'none' ? `import { connectDB } from './config/db.js';` : '',
12
12
  `import { errorHandler } from './middlewares/errorHandler.js';`,
13
13
  `import { notFound } from './middlewares/notFound.js';`,
14
- structure === 'mvc' ? `import routes from './routes/index.js';` : '',
15
- structure === 'modular' ? `import routes from './routes/index.js';` : '',
16
- structure === 'layered' ? `import routes from './routes/index.js';` : '',
14
+ `import routes from './routes/index.js';`,
17
15
  ].filter(Boolean).join('\n');
18
16
 
19
17
  const dbInit = database !== 'none' ? `\n// Connect to database\nconnectDB();\n` : '';
@@ -47,6 +47,14 @@ export async function generateProject(answers, targetDir) {
47
47
  // 7. server.js entry
48
48
  await fs.writeFile(path.join(targetDir, `server.${ext}`), serverContent(answers));
49
49
 
50
+ // 7b. Swagger config (only if swagger selected)
51
+ if (answers.extras.includes('swagger')) {
52
+ await fs.writeFile(
53
+ path.join(targetDir, 'src', 'config', `swagger.${ext}`),
54
+ swaggerConfigContent(answers)
55
+ );
56
+ }
57
+
50
58
  // 8. DB config
51
59
  if (answers.database !== 'none') {
52
60
  spinner.text = 'Setting up database connection...';
@@ -127,24 +135,52 @@ coverage/
127
135
  }
128
136
 
129
137
  function serverContent(answers) {
130
- const ext = answers.language === 'ts' ? "import app from './src/app.js';" : "import app from './src/app.js';";
131
- return `${ext}
132
- import { env } from './src/config/env.js';
138
+ const ext = answers.language === 'ts' ? 'ts' : 'js';
139
+ return `import 'dotenv/config';
140
+ import app from './src/app.js';
133
141
 
134
- const PORT = env.PORT || 3000;
142
+ const PORT = process.env.PORT || 3000;
143
+ const NODE_ENV = process.env.NODE_ENV || 'development';
135
144
 
136
145
  app.listen(PORT, () => {
137
- console.log(\`Server running on http://localhost:\${PORT}\`);
138
- console.log(\`Environment: \${env.NODE_ENV}\`);
146
+ console.log(\`✅ Server running on http://localhost:\${PORT}\`);
147
+ console.log(\`📦 Environment: \${NODE_ENV}\`);
139
148
  });
140
149
  `;
141
150
  }
142
151
 
152
+ function swaggerConfigContent(answers) {
153
+ return `import swaggerJsdoc from 'swagger-jsdoc';
154
+
155
+ const options = {
156
+ definition: {
157
+ openapi: '3.0.0',
158
+ info: {
159
+ title: '${answers.projectName} API',
160
+ version: '1.0.0',
161
+ description: 'API documentation for ${answers.projectName}',
162
+ },
163
+ servers: [
164
+ { url: \`http://localhost:\${process.env.PORT || 3000}/api/v1\`, description: 'Development' },
165
+ ],
166
+ components: {
167
+ securitySchemes: {
168
+ bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
169
+ },
170
+ },
171
+ },
172
+ apis: ['./src/routes/*.js'],
173
+ };
174
+
175
+ export const swaggerSpec = swaggerJsdoc(options);
176
+ `;
177
+ }
178
+
143
179
  function envConfigContent(answers) {
144
180
  return `export const env = {
145
- PORT: process.env.PORT || 3000,
181
+ PORT: process.env.PORT || 3000,
146
182
  NODE_ENV: process.env.NODE_ENV || 'development',
147
- ${answers.database === 'mongodb' ? ` MONGO_URI: process.env.MONGO_URI,\n` : ''}${answers.database === 'postgresql' || answers.database === 'mysql' ? ` DATABASE_URL: process.env.DATABASE_URL,\n` : ''}${answers.auth === 'jwt' ? ` JWT_SECRET: process.env.JWT_SECRET,\n JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '7d',\n JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET,\n JWT_REFRESH_EXPIRES_IN: process.env.JWT_REFRESH_EXPIRES_IN || '30d',\n` : ''}};
183
+ ${answers.database === 'mongodb' ? ` MONGO_URI: process.env.MONGO_URI,\n` : ''}${answers.database === 'postgresql' || answers.database === 'mysql' ? ` DATABASE_URL: process.env.DATABASE_URL,\n` : ''}${answers.auth === 'jwt' ? ` JWT_SECRET: process.env.JWT_SECRET,\n JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '7d',\n JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET,\n JWT_REFRESH_EXPIRES_IN: process.env.JWT_REFRESH_EXPIRES_IN || '30d',\n` : ''}};
148
184
  `;
149
185
  }
150
186