create-backlist 2.0.0 β 3.0.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/LICENSE +21 -0
- package/README.md +97 -0
- package/bin/index.js +13 -2
- package/package.json +1 -1
- package/src/generators/node.js +118 -92
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +89 -0
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +27 -0
- package/src/templates/node-ts-express/partials/Auth.routes.ts.ejs +15 -0
- package/src/templates/node-ts-express/partials/routes.ts.ejs +16 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2025] [W.A.H.ISHAN]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# π Create Backlist
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/create-backlist)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Tired of manually creating backend boilerplate every time you build a frontend? **`create-backlist`** is an intelligent CLI tool that analyzes your frontend project and automatically generates a backend with all the necessary routes and controllers, saving you hours of repetitive work.
|
|
7
|
+
|
|
8
|
+
It's not just another scaffolder; it's a **context-aware, dynamic code generator** that builds a backend tailor-made for your frontend's specific API needs.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## β¨ Key Features
|
|
13
|
+
|
|
14
|
+
- **π€ Intelligent Code Analysis:** Scans your frontend codebase (React, Vue, etc.) using Abstract Syntax Trees (ASTs) to detect API calls (`fetch` requests).
|
|
15
|
+
- **π Multi-Language Support:** Generate a backend in your preferred stack.
|
|
16
|
+
- β
**Currently Supports:**
|
|
17
|
+
- Node.js (with TypeScript & Express)
|
|
18
|
+
- C# (with ASP.NET Core Web API)
|
|
19
|
+
- β³ **Coming Soon:**
|
|
20
|
+
- Python (with FastAPI)
|
|
21
|
+
- Java (with Spring Boot)
|
|
22
|
+
- **β‘οΈ Fully Automated:** A single command handles everything from project scaffolding to dependency installation.
|
|
23
|
+
- **π§ Zero-Configuration:** No complex config files needed. Just run the command and answer a few simple questions.
|
|
24
|
+
- **π§Ό Clean Code Generation:** Creates a well-structured backend, ready for you to implement your business logic.
|
|
25
|
+
|
|
26
|
+
## π¦ Installation & Usage
|
|
27
|
+
|
|
28
|
+
No global installation needed! Just run this command inside your existing frontend project's root directory:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm create backlist@latest
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The tool will then guide you through an interactive setup process:
|
|
35
|
+
|
|
36
|
+
1. **Enter a name for your backend directory:** (default: `backend`)
|
|
37
|
+
2. **Select the backend stack:** (e.g., `Node.js (TypeScript, Express)`)
|
|
38
|
+
3. **Enter the path to your frontend `src` directory:** (default: `src`)
|
|
39
|
+
|
|
40
|
+
That's it! The tool will analyze your code, generate the backend in a new directory, and install all the necessary dependencies.
|
|
41
|
+
|
|
42
|
+
### Example
|
|
43
|
+
|
|
44
|
+
Let's say your frontend has this API call:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// in your React component
|
|
48
|
+
fetch('/api/products/123', { method: 'PUT' });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`create-backlist` will automatically generate a backend with a `products` controller and a `PUT` route for `products/:id`.
|
|
52
|
+
|
|
53
|
+
## π‘ How It's Different from Other Tools
|
|
54
|
+
|
|
55
|
+
| Tool | Approach | Use Case |
|
|
56
|
+
| --------------------- | -------------------------------------- | ----------------------------------------------------------- |
|
|
57
|
+
| **Express Generator** | Static Scaffolding | Quickly start a *new, empty* Express project. |
|
|
58
|
+
| **NestJS CLI** | Static Scaffolding & Code Generation | Start a *new, structured* NestJS project and add parts manually. |
|
|
59
|
+
| **`create-backlist`** | **Dynamic & Context-Aware Scaffolding** | Generate a backend that is **tailor-made** for an *existing* frontend. |
|
|
60
|
+
|
|
61
|
+
While traditional generators give you a blank canvas, `create-backlist` looks at your finished painting (the frontend) and builds the perfect frame (the backend) for it.
|
|
62
|
+
|
|
63
|
+
## πΊοΈ Roadmap
|
|
64
|
+
|
|
65
|
+
`create-backlist` is actively being developed. Here are some of the features planned for future releases:
|
|
66
|
+
|
|
67
|
+
- [ ] **Python Support:** Generate a backend using FastAPI.
|
|
68
|
+
- [ ] **Java Support:** Generate a backend using Spring Boot.
|
|
69
|
+
- [ ] **Database Model Generation:** Automatically create basic database models (e.g., Mongoose, Prisma) based on `POST`/`PUT` request bodies.
|
|
70
|
+
- [ ] **Authentication Boilerplate:** Add an option to generate basic JWT-based authentication routes (`/login`, `/register`).
|
|
71
|
+
|
|
72
|
+
Have an idea for a new feature? Feel free to [open an issue](https://github.com/WAH-ISHAN/create-backlist/issues) on GitHub!
|
|
73
|
+
|
|
74
|
+
## π οΈ Contributing
|
|
75
|
+
|
|
76
|
+
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
|
77
|
+
|
|
78
|
+
1. Fork the Project
|
|
79
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
80
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
81
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
82
|
+
5. Open a Pull Request
|
|
83
|
+
|
|
84
|
+
## π License
|
|
85
|
+
|
|
86
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
87
|
+
|
|
88
|
+
## π Acknowledgements
|
|
89
|
+
|
|
90
|
+
- **Babel** for the amazing AST parser.
|
|
91
|
+
- **Inquirer.js** for the interactive CLI prompts.
|
|
92
|
+
- **fs-extra** for making file system operations a breeze.
|
|
93
|
+
- **Google's Gemini** for assistance with brainstorming and debugging.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
_Built with β€οΈ by [W.A.H. ISHAN](https://github.com/WAH-ISHAN)._
|
package/bin/index.js
CHANGED
|
@@ -38,11 +38,21 @@ async function main() {
|
|
|
38
38
|
name: 'srcPath',
|
|
39
39
|
message: 'Enter the path to your frontend `src` directory:',
|
|
40
40
|
default: 'src',
|
|
41
|
+
},
|
|
42
|
+
// --- NEW QUESTION FOR V3.0 ---
|
|
43
|
+
{
|
|
44
|
+
type: 'confirm',
|
|
45
|
+
name: 'addAuth',
|
|
46
|
+
message: 'Do you want to add basic JWT authentication? (generates a User model, login/register routes)',
|
|
47
|
+
default: true,
|
|
48
|
+
// This question will only be asked if the user selects the Node.js stack
|
|
49
|
+
when: (answers) => answers.stack === 'node-ts-express'
|
|
41
50
|
}
|
|
42
51
|
]);
|
|
43
52
|
|
|
53
|
+
// Pass all answers to the options object
|
|
44
54
|
const options = {
|
|
45
|
-
...answers,
|
|
55
|
+
...answers,
|
|
46
56
|
projectDir: path.resolve(process.cwd(), answers.projectName),
|
|
47
57
|
frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
|
|
48
58
|
};
|
|
@@ -53,7 +63,7 @@ async function main() {
|
|
|
53
63
|
// --- Dispatcher Logic ---
|
|
54
64
|
switch (options.stack) {
|
|
55
65
|
case 'node-ts-express':
|
|
56
|
-
await generateNodeProject(options);
|
|
66
|
+
await generateNodeProject(options); // Pass the entire options object
|
|
57
67
|
break;
|
|
58
68
|
|
|
59
69
|
case 'dotnet-webapi':
|
|
@@ -61,6 +71,7 @@ async function main() {
|
|
|
61
71
|
throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
|
|
62
72
|
}
|
|
63
73
|
await generateDotnetProject(options);
|
|
74
|
+
|
|
64
75
|
break;
|
|
65
76
|
|
|
66
77
|
default:
|
package/package.json
CHANGED
package/src/generators/node.js
CHANGED
|
@@ -5,137 +5,163 @@ const path = require('path');
|
|
|
5
5
|
const { analyzeFrontend } = require('../analyzer');
|
|
6
6
|
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Generate a Node.js + TypeScript (Express) backend project automatically.
|
|
10
|
-
*/
|
|
11
8
|
async function generateNodeProject(options) {
|
|
12
|
-
const { projectDir, projectName, frontendSrcDir } = options;
|
|
9
|
+
const { projectDir, projectName, frontendSrcDir, addAuth } = options;
|
|
13
10
|
|
|
14
11
|
try {
|
|
15
|
-
// --- Step 1: Analyze Frontend ---
|
|
16
|
-
console.log(chalk.blue('
|
|
12
|
+
// --- Step 1: Analyze Frontend to get Endpoints and Schema Info ---
|
|
13
|
+
console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
|
|
17
14
|
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
18
|
-
|
|
19
15
|
if (endpoints.length > 0) {
|
|
20
|
-
console.log(chalk.green(`
|
|
16
|
+
console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
|
|
21
17
|
} else {
|
|
22
|
-
console.log(
|
|
23
|
-
chalk.yellow(' -> No API endpoints found. A basic project will be created.')
|
|
24
|
-
);
|
|
18
|
+
console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
// --- Step 2:
|
|
28
|
-
console.log(chalk.blue(' -> Scaffolding Node.js (Express + TS) project...'));
|
|
29
|
-
|
|
30
|
-
const baseDir = getTemplatePath('node-ts-express/base');
|
|
31
|
-
const serverTemplatePath = path.join(baseDir, 'server.ts');
|
|
32
|
-
const tsconfigTemplatePath = path.join(baseDir, 'tsconfig.json');
|
|
33
|
-
|
|
34
|
-
const destSrcDir = path.join(projectDir, 'src');
|
|
35
|
-
const serverDestPath = path.join(destSrcDir, 'server.ts');
|
|
36
|
-
const tsconfigDestPath = path.join(projectDir, 'tsconfig.json');
|
|
37
|
-
|
|
38
|
-
await fs.ensureDir(destSrcDir);
|
|
39
|
-
await fs.copy(serverTemplatePath, serverDestPath);
|
|
40
|
-
await fs.copy(tsconfigTemplatePath, tsconfigDestPath);
|
|
41
|
-
|
|
42
|
-
console.log(chalk.gray(' -> Base server.ts and tsconfig.json copied.'));
|
|
43
|
-
|
|
44
|
-
// --- Step 3: Generate package.json and routes.ts ---
|
|
45
|
-
await renderAndWrite(
|
|
46
|
-
getTemplatePath('node-ts-express/partials/package.json.ejs'),
|
|
47
|
-
path.join(projectDir, 'package.json'),
|
|
48
|
-
{ projectName }
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
await renderAndWrite(
|
|
52
|
-
getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
|
|
53
|
-
path.join(destSrcDir, 'routes.ts'),
|
|
54
|
-
{ endpoints }
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
console.log(chalk.gray(' -> package.json and routes.ts generated.'));
|
|
58
|
-
|
|
59
|
-
// --- Step 4: Analyze endpoints for models/controllers ---
|
|
21
|
+
// --- Step 2: Identify which Database Models to Generate ---
|
|
60
22
|
const modelsToGenerate = new Map();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (ep.schemaFields && !modelsToGenerate.has(ep.controllerName)) {
|
|
23
|
+
endpoints.forEach(ep => {
|
|
24
|
+
if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
|
|
64
25
|
modelsToGenerate.set(ep.controllerName, ep.schemaFields);
|
|
65
26
|
}
|
|
66
27
|
});
|
|
67
28
|
|
|
68
|
-
//
|
|
69
|
-
if (modelsToGenerate.
|
|
70
|
-
console.log(chalk.
|
|
29
|
+
// If auth is enabled, we MUST have a 'User' model.
|
|
30
|
+
if (addAuth && !modelsToGenerate.has('User')) {
|
|
31
|
+
console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
|
|
32
|
+
modelsToGenerate.set('User', { name: 'String', email: 'String', password: 'String' });
|
|
33
|
+
}
|
|
71
34
|
|
|
72
|
-
|
|
73
|
-
|
|
35
|
+
// --- Step 3: Scaffold Base Project Structure & Files ---
|
|
36
|
+
console.log(chalk.blue(' -> Scaffolding Node.js (Express + TS) project...'));
|
|
37
|
+
const destSrcDir = path.join(projectDir, 'src');
|
|
38
|
+
await fs.ensureDir(destSrcDir);
|
|
39
|
+
await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'));
|
|
40
|
+
await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
|
|
41
|
+
|
|
42
|
+
// --- Step 4: Prepare and Write package.json ---
|
|
43
|
+
const packageJsonContent = JSON.parse(
|
|
44
|
+
await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName })
|
|
45
|
+
);
|
|
74
46
|
|
|
75
|
-
|
|
76
|
-
|
|
47
|
+
if (modelsToGenerate.size > 0 || addAuth) {
|
|
48
|
+
packageJsonContent.dependencies['mongoose'] = '^7.5.0';
|
|
49
|
+
}
|
|
50
|
+
if (addAuth) {
|
|
51
|
+
packageJsonContent.dependencies['jsonwebtoken'] = '^9.0.2';
|
|
52
|
+
packageJsonContent.dependencies['bcryptjs'] = '^2.4.3';
|
|
53
|
+
packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.2';
|
|
54
|
+
packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.2';
|
|
55
|
+
}
|
|
56
|
+
await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 });
|
|
57
|
+
|
|
58
|
+
// --- Step 5: Generate Models and Controllers ---
|
|
59
|
+
if (modelsToGenerate.size > 0) {
|
|
60
|
+
console.log(chalk.blue(' -> Generating database models and controllers...'));
|
|
61
|
+
await fs.ensureDir(path.join(destSrcDir, 'models'));
|
|
62
|
+
await fs.ensureDir(path.join(destSrcDir, 'controllers'));
|
|
63
|
+
|
|
64
|
+
for (let [modelName, schema] of modelsToGenerate.entries()) {
|
|
65
|
+
if (addAuth && modelName === 'User') {
|
|
66
|
+
schema = { name: 'String', email: 'String', password: 'String', ...schema };
|
|
67
|
+
}
|
|
77
68
|
await renderAndWrite(
|
|
78
69
|
getTemplatePath('node-ts-express/partials/Model.ts.ejs'),
|
|
79
|
-
path.join(
|
|
70
|
+
path.join(destSrcDir, 'models', `${modelName}.model.ts`),
|
|
80
71
|
{ modelName, schema }
|
|
81
72
|
);
|
|
82
|
-
|
|
83
|
-
// Generate Controller file
|
|
84
73
|
await renderAndWrite(
|
|
85
74
|
getTemplatePath('node-ts-express/partials/Controller.ts.ejs'),
|
|
86
|
-
path.join(
|
|
75
|
+
path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`),
|
|
87
76
|
{ modelName }
|
|
88
77
|
);
|
|
89
78
|
}
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Step 6 (v3.0): Generate Authentication Boilerplate ---
|
|
82
|
+
if (addAuth) {
|
|
83
|
+
console.log(chalk.blue(' -> Generating authentication boilerplate...'));
|
|
84
|
+
await fs.ensureDir(path.join(destSrcDir, 'routes'));
|
|
85
|
+
await fs.ensureDir(path.join(destSrcDir, 'middleware'));
|
|
86
|
+
|
|
87
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), {});
|
|
88
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), {});
|
|
89
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), {});
|
|
90
|
+
|
|
91
|
+
// Modify the User model to add password hashing
|
|
92
|
+
const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts');
|
|
93
|
+
if (await fs.pathExists(userModelPath)) {
|
|
94
|
+
let userModelContent = await fs.readFile(userModelPath, 'utf-8');
|
|
95
|
+
if (!userModelContent.includes('bcryptjs')) {
|
|
96
|
+
userModelContent = userModelContent.replace(`import mongoose, { Schema, Document } from 'mongoose';`, `import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';`);
|
|
97
|
+
const preSaveHook = `
|
|
98
|
+
// Hash password before saving
|
|
99
|
+
UserSchema.pre('save', async function(next) {
|
|
100
|
+
if (!this.isModified('password')) {
|
|
101
|
+
return next();
|
|
102
|
+
}
|
|
103
|
+
const salt = await bcrypt.genSalt(10);
|
|
104
|
+
this.password = await bcrypt.hash(this.password, salt);
|
|
105
|
+
next();
|
|
106
|
+
});
|
|
107
|
+
`;
|
|
108
|
+
userModelContent = userModelContent.replace(`// Create and export the Model`, `${preSaveHook}\n// Create and export the Model`);
|
|
109
|
+
await fs.writeFile(userModelPath, userModelContent);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
92
112
|
}
|
|
93
113
|
|
|
94
|
-
// --- Step
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
// --- Step 7: Generate the Main Route File ---
|
|
115
|
+
console.log(chalk.gray(' -> Generating dynamic API routes...'));
|
|
116
|
+
await renderAndWrite(getTemplatePath('node-ts-express/partials/routes.ts.ejs'), path.join(destSrcDir, 'routes.ts'), { endpoints, addAuth });
|
|
117
|
+
|
|
118
|
+
// --- Step 8: Inject Logic into Main Server File ---
|
|
119
|
+
let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8');
|
|
120
|
+
|
|
121
|
+
let dbConnectionCode = '';
|
|
122
|
+
if (modelsToGenerate.size > 0 || addAuth) {
|
|
123
|
+
dbConnectionCode = `
|
|
124
|
+
// --- Database Connection ---
|
|
125
|
+
import mongoose from 'mongoose';
|
|
126
|
+
const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
|
|
127
|
+
mongoose.connect(MONGO_URI)
|
|
128
|
+
.then(() => console.log('MongoDB Connected...'))
|
|
129
|
+
.catch(err => console.error('MongoDB Connection Error:', err));
|
|
130
|
+
// -------------------------
|
|
131
|
+
`;
|
|
97
132
|
}
|
|
98
133
|
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
-
await fs.writeFile(serverDestPath, serverFileContent);
|
|
134
|
+
let authRoutesInjector = '';
|
|
135
|
+
if (addAuth) {
|
|
136
|
+
authRoutesInjector = `import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n`;
|
|
137
|
+
}
|
|
105
138
|
|
|
106
|
-
|
|
139
|
+
serverFileContent = serverFileContent
|
|
140
|
+
.replace("dotenv.config();", `dotenv.config();\n${dbConnectionCode}`)
|
|
141
|
+
.replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';\napp.use('/api', apiRoutes);`); // Changed to /api
|
|
142
|
+
|
|
143
|
+
await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent);
|
|
107
144
|
|
|
108
|
-
// --- Step
|
|
109
|
-
console.log(chalk.magenta(' -> Installing dependencies
|
|
145
|
+
// --- Step 9: Install All Dependencies ---
|
|
146
|
+
console.log(chalk.magenta(' -> Installing all dependencies... This might take a moment.'));
|
|
110
147
|
await execa('npm', ['install'], { cwd: projectDir });
|
|
111
148
|
|
|
112
|
-
// --- Step
|
|
113
|
-
if (modelsToGenerate.size > 0) {
|
|
114
|
-
console.log(chalk.gray(' -> Adding Mongoose to dependencies...'));
|
|
115
|
-
|
|
116
|
-
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
117
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
118
|
-
packageJson.dependencies = packageJson.dependencies || {};
|
|
119
|
-
packageJson.dependencies['mongoose'] = '^7.5.0';
|
|
120
|
-
|
|
121
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
122
|
-
|
|
123
|
-
console.log(chalk.magenta(' -> Installing new dependencies (mongoose)...'));
|
|
124
|
-
await execa('npm', ['install'], { cwd: projectDir });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// --- Step 9: Generate README ---
|
|
149
|
+
// --- Step 10: Generate Final Files (README, .env.example) ---
|
|
128
150
|
await renderAndWrite(
|
|
129
151
|
getTemplatePath('node-ts-express/partials/README.md.ejs'),
|
|
130
152
|
path.join(projectDir, 'README.md'),
|
|
131
153
|
{ projectName }
|
|
132
154
|
);
|
|
155
|
+
|
|
156
|
+
if (addAuth) {
|
|
157
|
+
const envExampleContent = `PORT=8000\nMONGO_URI=mongodb://127.0.0.1:27017/${projectName}\nJWT_SECRET=your_super_secret_jwt_key_123`;
|
|
158
|
+
await fs.writeFile(path.join(projectDir, '.env.example'), envExampleContent);
|
|
159
|
+
}
|
|
160
|
+
|
|
133
161
|
|
|
134
|
-
console.log(chalk.green('β
Project generation completed successfully!'));
|
|
135
162
|
} catch (error) {
|
|
136
|
-
|
|
137
|
-
throw error; // Pass to main CLI handler
|
|
163
|
+
throw error;
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
|
|
141
|
-
module.exports = { generateNodeProject };
|
|
167
|
+
module.exports = { generateNodeProject };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
|
|
2
|
+
import { Request, Response } from 'express';
|
|
3
|
+
import bcrypt from 'bcryptjs';
|
|
4
|
+
import jwt from 'jsonwebtoken';
|
|
5
|
+
import User, { IUser } from '../models/User.model'; // We assume the model is named 'User'
|
|
6
|
+
|
|
7
|
+
// @desc Register a new user
|
|
8
|
+
export const registerUser = async (req: Request, res: Response) => {
|
|
9
|
+
const { name, email, password } = req.body;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Check if user already exists
|
|
13
|
+
let user = await User.findOne({ email });
|
|
14
|
+
if (user) {
|
|
15
|
+
return res.status(400).json({ message: 'User already exists' });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Create a new user instance
|
|
19
|
+
user = new User({
|
|
20
|
+
name,
|
|
21
|
+
email,
|
|
22
|
+
password, // Password will be hashed by the pre-save hook in the model
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Save the user to the database
|
|
26
|
+
await user.save();
|
|
27
|
+
|
|
28
|
+
// Create JWT Payload
|
|
29
|
+
const payload = {
|
|
30
|
+
user: {
|
|
31
|
+
id: user.id,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Sign the token
|
|
36
|
+
jwt.sign(
|
|
37
|
+
payload,
|
|
38
|
+
process.env.JWT_SECRET as string,
|
|
39
|
+
{ expiresIn: '5h' }, // Token expires in 5 hours
|
|
40
|
+
(err, token) => {
|
|
41
|
+
if (err) throw err;
|
|
42
|
+
res.status(201).json({ token });
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error);
|
|
47
|
+
res.status(500).send('Server Error');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// @desc Authenticate user & get token (Login)
|
|
52
|
+
export const loginUser = async (req: Request, res: Response) => {
|
|
53
|
+
const { email, password } = req.body;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// Check if user exists
|
|
57
|
+
const user = await User.findOne({ email });
|
|
58
|
+
if (!user) {
|
|
59
|
+
return res.status(400).json({ message: 'Invalid Credentials' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Compare entered password with stored hashed password
|
|
63
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
64
|
+
if (!isMatch) {
|
|
65
|
+
return res.status(400).json({ message: 'Invalid Credentials' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create JWT Payload
|
|
69
|
+
const payload = {
|
|
70
|
+
user: {
|
|
71
|
+
id: user.id,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Sign the token
|
|
76
|
+
jwt.sign(
|
|
77
|
+
payload,
|
|
78
|
+
process.env.JWT_SECRET as string,
|
|
79
|
+
{ expiresIn: '5h' },
|
|
80
|
+
(err, token) => {
|
|
81
|
+
if (err) throw err;
|
|
82
|
+
res.json({ token });
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(error);
|
|
87
|
+
res.status(500).send('Server Error');
|
|
88
|
+
}
|
|
89
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import jwt from 'jsonwebtoken';
|
|
4
|
+
|
|
5
|
+
// Extend the default Request interface to include our 'user' property
|
|
6
|
+
interface AuthRequest extends Request {
|
|
7
|
+
user?: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const protect = (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
11
|
+
// Get token from header
|
|
12
|
+
const token = req.header('x-auth-token');
|
|
13
|
+
|
|
14
|
+
// Check if not token
|
|
15
|
+
if (!token) {
|
|
16
|
+
return res.status(401).json({ message: 'No token, authorization denied' });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Verify token
|
|
20
|
+
try {
|
|
21
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
|
|
22
|
+
req.user = decoded.user;
|
|
23
|
+
next();
|
|
24
|
+
} catch (err) {
|
|
25
|
+
res.status(401).json({ message: 'Token is not valid' });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import { registerUser, loginUser } from '../controllers/Auth.controller';
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
// @route POST /api/auth/register
|
|
8
|
+
// @desc Register a new user
|
|
9
|
+
router.post('/register', registerUser);
|
|
10
|
+
|
|
11
|
+
// @route POST /api/auth/login
|
|
12
|
+
// @desc Authenticate user and get token
|
|
13
|
+
router.post('/login', loginUser);
|
|
14
|
+
|
|
15
|
+
export default router;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Auto-generated by create-backlist on <%= new Date().toISOString() %>
|
|
1
|
+
// Auto-generated by create-backlist v3.0 on <%= new Date().toISOString() %>
|
|
2
2
|
import { Router, Request, Response } from 'express';
|
|
3
3
|
<%# Create a unique set of controller names from the endpoints array %>
|
|
4
4
|
<% const controllersToImport = new Set(endpoints.map(ep => ep.controllerName).filter(name => name !== 'Default')); %>
|
|
@@ -8,6 +8,11 @@ import { Router, Request, Response } from 'express';
|
|
|
8
8
|
import * as <%= controller %>Controller from '../controllers/<%= controller %>.controller';
|
|
9
9
|
<% } %>
|
|
10
10
|
|
|
11
|
+
<%# Import the protect middleware only if authentication is enabled %>
|
|
12
|
+
<% if (addAuth) { %>
|
|
13
|
+
import { protect } from '../middleware/Auth.middleware';
|
|
14
|
+
<% } %>
|
|
15
|
+
|
|
11
16
|
const router = Router();
|
|
12
17
|
|
|
13
18
|
<%# Loop through each endpoint found by the analyzer %>
|
|
@@ -19,41 +24,40 @@ const router = Router();
|
|
|
19
24
|
let handlerFunction;
|
|
20
25
|
|
|
21
26
|
// --- LOGIC TO MAP ENDPOINT TO A CRUD CONTROLLER FUNCTION ---
|
|
22
|
-
// This logic assumes a standard RESTful API structure.
|
|
23
|
-
|
|
24
27
|
if (controllerName !== 'Default') {
|
|
25
28
|
if (endpoint.method === 'POST' && !expressPath.includes(':')) {
|
|
26
|
-
// e.g., POST /users -> create a new user
|
|
27
29
|
handlerFunction = `${controllerName}Controller.create${controllerName}`;
|
|
28
30
|
} else if (endpoint.method === 'GET' && !expressPath.includes(':')) {
|
|
29
|
-
// e.g., GET /users -> get all users
|
|
30
31
|
handlerFunction = `${controllerName}Controller.getAll${controllerName}s`;
|
|
31
32
|
} else if (endpoint.method === 'GET' && expressPath.includes(':')) {
|
|
32
|
-
// e.g., GET /users/:id -> get a single user by ID
|
|
33
33
|
handlerFunction = `${controllerName}Controller.get${controllerName}ById`;
|
|
34
34
|
} else if (endpoint.method === 'PUT' && expressPath.includes(':')) {
|
|
35
|
-
// e.g., PUT /users/:id -> update a user by ID
|
|
36
35
|
handlerFunction = `${controllerName}Controller.update${controllerName}ById`;
|
|
37
36
|
} else if (endpoint.method === 'DELETE' && expressPath.includes(':')) {
|
|
38
|
-
// e.g., DELETE /users/:id -> delete a user by ID
|
|
39
37
|
handlerFunction = `${controllerName}Controller.delete${controllerName}ById`;
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
// If no specific CRUD function matches,
|
|
44
|
-
// create a simple placeholder function.
|
|
41
|
+
// If no specific CRUD function matches, create a placeholder handler.
|
|
45
42
|
if (!handlerFunction) {
|
|
46
43
|
handlerFunction = `(req: Request, res: Response) => {
|
|
47
|
-
// TODO: Implement logic for this custom endpoint
|
|
48
44
|
res.status(501).json({ message: 'Handler not implemented for <%= endpoint.method %> <%= expressPath %>' });
|
|
49
45
|
}`;
|
|
50
46
|
}
|
|
47
|
+
|
|
48
|
+
// --- V3.0 AUTH LOGIC: Decide if the route should be protected ---
|
|
49
|
+
// We protect all routes that modify data (POST, PUT, DELETE) if auth is enabled.
|
|
50
|
+
// We leave GET routes public by default. This is a common pattern.
|
|
51
|
+
const middleware = (addAuth && (endpoint.method === 'POST' || endpoint.method === 'PUT' || endpoint.method === 'DELETE'))
|
|
52
|
+
? 'protect, '
|
|
53
|
+
: '';
|
|
51
54
|
%>
|
|
52
55
|
/**
|
|
53
56
|
* Route for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
|
|
54
57
|
* Mapped to: <%- handlerFunction.includes('=>') ? 'Inline Handler' : handlerFunction %>
|
|
58
|
+
* Protected: <%= middleware ? 'Yes' : 'No' %>
|
|
55
59
|
*/
|
|
56
|
-
router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', <%- handlerFunction %>);
|
|
60
|
+
router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', <%- middleware %><%- handlerFunction %>);
|
|
57
61
|
|
|
58
62
|
<% }); %>
|
|
59
63
|
|