create-backlist 6.2.3 → 7.3.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.
Files changed (52) hide show
  1. package/README.md +1 -10
  2. package/bin/index.js +470 -140
  3. package/package.json +11 -7
  4. package/src/ai-agent.js +171 -0
  5. package/src/analyzer.js +137 -22
  6. package/src/generators/dotnet.js +134 -133
  7. package/src/generators/java.js +248 -233
  8. package/src/generators/js.js +346 -0
  9. package/src/generators/nestjs.js +278 -0
  10. package/src/generators/node.js +57 -26
  11. package/src/generators/python.js +86 -104
  12. package/src/generators/template.js +10 -8
  13. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
  14. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
  15. package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
  16. package/src/templates/js-express/base/server.js +59 -0
  17. package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
  18. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
  19. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
  20. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
  21. package/src/templates/js-express/partials/controller.js.ejs +53 -0
  22. package/src/templates/js-express/partials/db.js.ejs +19 -0
  23. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
  24. package/src/templates/js-express/partials/model.js.ejs +18 -0
  25. package/src/templates/js-express/partials/package.json.ejs +17 -0
  26. package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
  27. package/src/templates/js-express/partials/routes.js.ejs +19 -0
  28. package/src/templates/js-express/partials/seeder.js.ejs +103 -0
  29. package/src/templates/js-express/partials/service.js.ejs +51 -0
  30. package/src/templates/js-express/partials/swagger.js.ejs +30 -0
  31. package/src/templates/js-express/partials/test.js.ejs +46 -0
  32. package/src/templates/nestjs/base/app.module.ts +9 -0
  33. package/src/templates/nestjs/base/main.ts +23 -0
  34. package/src/templates/nestjs/base/tsconfig.json +21 -0
  35. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
  36. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
  37. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
  38. package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
  39. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
  40. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
  41. package/src/templates/nestjs/partials/module.ts.ejs +10 -0
  42. package/src/templates/nestjs/partials/package.json.ejs +27 -0
  43. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
  44. package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
  45. package/src/templates/nestjs/partials/service.ts.ejs +67 -0
  46. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
  47. package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -0
  48. package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -0
  49. package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -0
  50. package/src/utils.js +3 -5
  51. /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
  52. /package/src/templates/{node-ts-express → dotnet}/partials/Model.cs.ejs +0 -0
@@ -1,11 +1,11 @@
1
- const chalk = require("chalk");
2
- const { execa } = require("execa");
3
- const fs = require("fs-extra");
4
- const path = require("path");
5
- const ejs = require("ejs");
1
+ import chalk from "chalk";
2
+ import { execa } from "execa";
3
+ import fs from "fs-extra";
4
+ import path from "node:path";
5
+ import ejs from "ejs";
6
6
 
7
- const { analyzeFrontend } = require("../analyzer");
8
- const { renderAndWrite, getTemplatePath } = require("./template");
7
+ import { analyzeFrontend } from "../analyzer.js";
8
+ import { renderAndWrite, getTemplatePath } from "./template.js";
9
9
 
10
10
  function stripQuery(p) {
11
11
  return String(p || "").split("?")[0];
@@ -71,7 +71,7 @@ function sanitizeEndpoints(endpoints) {
71
71
  });
72
72
  }
73
73
 
74
- async function generateNodeProject(options) {
74
+ export async function generateNodeProject(options) {
75
75
  const {
76
76
  projectDir,
77
77
  projectName,
@@ -181,13 +181,20 @@ async function generateNodeProject(options) {
181
181
 
182
182
  await fs.writeJson(path.join(projectDir, "package.json"), packageJsonContent, { spaces: 2 });
183
183
 
184
- // --- Step 5: DB + Controllers ---
184
+ // --- Step 5: DB + Hexagonal Architecture Scaffolding ---
185
185
  if (modelsToGenerate.size > 0) {
186
- await fs.ensureDir(path.join(destSrcDir, "controllers"));
186
+ const portDir = path.join(destSrcDir, "application", "ports", "controllers");
187
+ const serviceDir = path.join(destSrcDir, "domain", "services");
188
+ const repoDir = path.join(destSrcDir, "infrastructure", "adapters", "repositories");
189
+ const modelDir = path.join(destSrcDir, "domain", "models");
190
+
191
+ await fs.ensureDir(portDir);
192
+ await fs.ensureDir(serviceDir);
193
+ await fs.ensureDir(repoDir);
187
194
 
188
195
  if (dbType === "mongoose") {
189
- console.log(chalk.blue(" -> Generating Mongoose models and controllers..."));
190
- await fs.ensureDir(path.join(destSrcDir, "models"));
196
+ console.log(chalk.blue(" -> Generating Mongoose domain models..."));
197
+ await fs.ensureDir(modelDir);
191
198
 
192
199
  for (const [modelName, modelData] of modelsToGenerate.entries()) {
193
200
  const schema = (modelData.fields || []).reduce((acc, field) => {
@@ -196,28 +203,54 @@ async function generateNodeProject(options) {
196
203
  }, {});
197
204
  await renderAndWrite(
198
205
  getTemplatePath("node-ts-express/partials/Model.ts.ejs"),
199
- path.join(destSrcDir, "models", `${modelName}.model.ts`),
206
+ path.join(modelDir, `${modelName}.model.ts`),
200
207
  { modelName, schema, projectName }
201
208
  );
202
209
  }
203
210
  } else if (dbType === "prisma") {
204
211
  console.log(chalk.blue(" -> Generating Prisma schema..."));
205
212
  await fs.ensureDir(path.join(projectDir, "prisma"));
206
- await renderAndWrite(
207
- getTemplatePath("node-ts-express/partials/PrismaSchema.prisma.ejs"),
208
- path.join(projectDir, "prisma", "schema.prisma"),
209
- { modelsToGenerate: Array.from(modelsToGenerate.values()) }
210
- );
213
+ // Check if we already have a generated schema from AI Pass 1 (options.aiBlocks.prismaSchema)
214
+ if (options.aiBlocks && options.aiBlocks.prismaSchema) {
215
+ await fs.writeFile(path.join(projectDir, "prisma", "schema.prisma"), options.aiBlocks.prismaSchema);
216
+ } else {
217
+ await renderAndWrite(
218
+ getTemplatePath("node-ts-express/partials/PrismaSchema.prisma.ejs"),
219
+ path.join(projectDir, "prisma", "schema.prisma"),
220
+ { modelsToGenerate: Array.from(modelsToGenerate.values()) }
221
+ );
222
+ }
211
223
  }
212
224
 
213
- console.log(chalk.blue(" -> Generating controllers..."));
225
+ console.log(chalk.blue(" -> Generating Hexagonal Architecture layers (Controllers, Services, Repositories)..."));
214
226
  for (const [modelName] of modelsToGenerate.entries()) {
215
- const templateFile = dbType === "mongoose" ? "Controller.ts.ejs" : "PrismaController.ts.ejs";
216
227
  if (modelName !== "Auth") {
228
+ const blockData = {
229
+ modelName,
230
+ projectName,
231
+ dbType,
232
+ aiSecurityConfig: options.aiBlocks?.aiSecurityConfig,
233
+ aiDbRelations: options.aiBlocks?.aiDbRelations,
234
+ aiValidationLogic: options.aiBlocks?.aiValidationLogic
235
+ };
236
+
237
+ // Controller (Port)
238
+ await renderAndWrite(
239
+ getTemplatePath(`node-ts-express/partials/HexController.ts.ejs`),
240
+ path.join(portDir, `${modelName}.controller.ts`),
241
+ blockData
242
+ );
243
+ // Service (Domain)
217
244
  await renderAndWrite(
218
- getTemplatePath(`node-ts-express/partials/${templateFile}`),
219
- path.join(destSrcDir, "controllers", `${modelName}.controller.ts`),
220
- { modelName, projectName }
245
+ getTemplatePath(`node-ts-express/partials/HexService.ts.ejs`),
246
+ path.join(serviceDir, `${modelName}.service.ts`),
247
+ blockData
248
+ );
249
+ // Repository (Adapter)
250
+ await renderAndWrite(
251
+ getTemplatePath(`node-ts-express/partials/HexRepository.ts.ejs`),
252
+ path.join(repoDir, `${modelName}.repository.ts`),
253
+ blockData
221
254
  );
222
255
  }
223
256
  }
@@ -369,6 +402,4 @@ app.use('/api', apiRoutes);`
369
402
  } catch (error) {
370
403
  throw error;
371
404
  }
372
- }
373
-
374
- module.exports = { generateNodeProject };
405
+ }
@@ -1,104 +1,86 @@
1
- const chalk = require('chalk');
2
- const { execa } = require('execa');
3
- const fs = require('fs-extra');
4
- const path = require('path');
5
- const { analyzeFrontend } = require('../analyzer');
6
- const { renderAndWrite, getTemplatePath } = require('./template');
7
-
8
- async function generatePythonProject(options) {
9
- const { projectDir, projectName, frontendSrcDir } = options;
10
-
11
- try {
12
- // --- Step 1: Analysis & Model Identification ---
13
- console.log(chalk.blue(' -> Analyzing frontend for Python (FastAPI) backend...'));
14
- const endpoints = await analyzeFrontend(frontendSrcDir);
15
- const modelsToGenerate = new Map();
16
- endpoints.forEach(ep => {
17
- if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
18
- modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
19
- }
20
- });
21
-
22
- // Add a default User model if none was detected but auth might be added later
23
- if (!modelsToGenerate.has('User')) {
24
- modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String'}] });
25
- }
26
-
27
- // --- Step 2: Scaffold Base Python Project Directories ---
28
- console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project structure...'));
29
- const appDir = path.join(projectDir, 'app');
30
- const coreDir = path.join(appDir, 'core');
31
- const dbDir = path.join(appDir, 'db'); // For DB connection
32
- const modelsDir = path.join(appDir, 'models');
33
- const schemasDir = path.join(appDir, 'schemas');
34
- const routesDir = path.join(appDir, 'routers');
35
-
36
- await fs.ensureDir(appDir);
37
- await fs.ensureDir(coreDir);
38
- await fs.ensureDir(dbDir);
39
- await fs.ensureDir(modelsDir);
40
- await fs.ensureDir(schemasDir);
41
- await fs.ensureDir(routesDir);
42
-
43
- // --- Step 3: Generate All Python Files from Templates ---
44
- const controllers = Array.from(modelsToGenerate.keys());
45
-
46
- // Generate main application file
47
- await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
48
- // Generate dependency file
49
- await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
50
-
51
- // Generate core files (config, security)
52
- await renderAndWrite(getTemplatePath('python-fastapi/app/core/config.py.ejs'), path.join(coreDir, 'config.py'), { projectName });
53
- await renderAndWrite(getTemplatePath('python-fastapi/app/core/security.py.ejs'), path.join(coreDir, 'security.py'), {});
54
-
55
- // Generate DB connection and base model
56
- await renderAndWrite(getTemplatePath('python-fastapi/app/db.py.ejs'), path.join(appDir, 'db.py'), {});
57
-
58
- // Generate model and schema files for User (for auth)
59
- await renderAndWrite(getTemplatePath('python-fastapi/app/models/user.py.ejs'), path.join(modelsDir, 'user.py'), {});
60
- await renderAndWrite(getTemplatePath('python-fastapi/app/schemas/user.py.ejs'), path.join(schemasDir, 'user.py'), {});
61
-
62
- // Generate router for auth
63
- await renderAndWrite(getTemplatePath('python-fastapi/app/routers/auth.py.ejs'), path.join(routesDir, 'auth.py'), {});
64
-
65
- // Generate router for each detected model
66
- for (const [modelName, modelData] of modelsToGenerate.entries()) {
67
- if(modelName.toLowerCase() !== 'user') { // User model is handled separately
68
- // In a full implementation, you'd have generic model/schema templates too
69
- }
70
- await renderAndWrite(getTemplatePath('python-fastapi/app/routers/model_routes.py.ejs'), path.join(routesDir, `${modelName.toLowerCase()}_routes.py`), { modelName, schema: modelData });
71
- }
72
-
73
- // --- Step 4: Setup Virtual Environment and Install Dependencies ---
74
- console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
75
- await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
76
-
77
- const pipPath = process.platform === 'win32' ? path.join('venv', 'Scripts', 'pip') : path.join('venv', 'bin', 'pip');
78
- await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
79
-
80
- // --- Step 5: Generate Docker and .env files ---
81
- await renderAndWrite(getTemplatePath('python-fastapi/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), {});
82
- await renderAndWrite(getTemplatePath('python-fastapi/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName });
83
-
84
- const envContent = `DATABASE_URL="postgresql://postgres:password@db:5432/${projectName}"\nJWT_SECRET="a_very_secret_key_change_this"`;
85
- await fs.writeFile(path.join(projectDir, '.env'), envContent);
86
- await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
87
-
88
-
89
- console.log(chalk.green(' -> Python (FastAPI) backend generation is complete!'));
90
- console.log(chalk.yellow('\nTo run your new Python backend with Docker:'));
91
- console.log(chalk.cyan(' 1. Make sure Docker Desktop is running.'));
92
- console.log(chalk.cyan(' 2. Run: `docker-compose up --build`'));
93
- console.log(chalk.cyan(' 3. API will be available at http://localhost:8000 and docs at http://localhost:8000/docs'));
94
-
95
-
96
- } catch (error) {
97
- if (error.code === 'ENOENT') {
98
- throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
99
- }
100
- throw error;
101
- }
102
- }
103
-
104
- module.exports = { generatePythonProject };
1
+ import chalk from 'chalk';
2
+ import { execa } from 'execa';
3
+ import fs from 'fs-extra';
4
+ import path from 'node:path';
5
+ import { analyzeFrontend } from '../analyzer.js';
6
+ import { renderAndWrite, getTemplatePath } from './template.js';
7
+
8
+ export async function generatePythonProject(options) {
9
+ const { projectDir, projectName, frontendSrcDir } = options;
10
+
11
+ try {
12
+ console.log(chalk.blue(' -> Analyzing frontend for Python (FastAPI) backend...'));
13
+ const endpoints = await analyzeFrontend(frontendSrcDir);
14
+ const modelsToGenerate = new Map();
15
+ endpoints.forEach(ep => {
16
+ if (ep.schemaFields && ep.controllerName !== 'Default' && !modelsToGenerate.has(ep.controllerName)) {
17
+ modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields: Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type })) });
18
+ }
19
+ });
20
+
21
+ if (!modelsToGenerate.has('User')) {
22
+ modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String' }] });
23
+ }
24
+
25
+ console.log(chalk.blue(' -> Scaffolding Python (FastAPI) project structure...'));
26
+ const appDir = path.join(projectDir, 'app');
27
+ const coreDir = path.join(appDir, 'core');
28
+ const dbDir = path.join(appDir, 'db');
29
+ const modelsDir = path.join(appDir, 'models');
30
+ const schemasDir = path.join(appDir, 'schemas');
31
+ const routesDir = path.join(appDir, 'routers');
32
+
33
+ await fs.ensureDir(appDir);
34
+ await fs.ensureDir(coreDir);
35
+ await fs.ensureDir(dbDir);
36
+ await fs.ensureDir(modelsDir);
37
+ await fs.ensureDir(schemasDir);
38
+ await fs.ensureDir(routesDir);
39
+
40
+ const controllers = Array.from(modelsToGenerate.keys());
41
+
42
+ await renderAndWrite(getTemplatePath('python-fastapi/main.py.ejs'), path.join(projectDir, 'app', 'main.py'), { projectName, controllers });
43
+ await renderAndWrite(getTemplatePath('python-fastapi/requirements.txt.ejs'), path.join(projectDir, 'requirements.txt'), {});
44
+
45
+ await renderAndWrite(getTemplatePath('python-fastapi/app/core/config.py.ejs'), path.join(coreDir, 'config.py'), { projectName });
46
+ await renderAndWrite(getTemplatePath('python-fastapi/app/core/security.py.ejs'), path.join(coreDir, 'security.py'), {});
47
+
48
+ await renderAndWrite(getTemplatePath('python-fastapi/app/db.py.ejs'), path.join(appDir, 'db.py'), {});
49
+
50
+ await renderAndWrite(getTemplatePath('python-fastapi/app/models/user.py.ejs'), path.join(modelsDir, 'user.py'), {});
51
+ await renderAndWrite(getTemplatePath('python-fastapi/app/schemas/user.py.ejs'), path.join(schemasDir, 'user.py'), {});
52
+
53
+ await renderAndWrite(getTemplatePath('python-fastapi/app/routers/auth.py.ejs'), path.join(routesDir, 'auth.py'), {});
54
+
55
+ for (const [modelName, modelData] of modelsToGenerate.entries()) {
56
+ if (modelName.toLowerCase() !== 'user') {
57
+ await renderAndWrite(getTemplatePath('python-fastapi/app/routers/model_routes.py.ejs'), path.join(routesDir, `${modelName.toLowerCase()}_routes.py`), { modelName, schema: modelData });
58
+ }
59
+ }
60
+
61
+ console.log(chalk.magenta(' -> Setting up virtual environment and installing dependencies...'));
62
+ await execa('python', ['-m', 'venv', 'venv'], { cwd: projectDir });
63
+
64
+ const pipPath = process.platform === 'win32' ? path.join('venv', 'Scripts', 'pip') : path.join('venv', 'bin', 'pip');
65
+ await execa(path.join(projectDir, pipPath), ['install', '-r', 'requirements.txt'], { cwd: projectDir });
66
+
67
+ await renderAndWrite(getTemplatePath('python-fastapi/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), {});
68
+ await renderAndWrite(getTemplatePath('python-fastapi/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName });
69
+
70
+ const envContent = `DATABASE_URL="postgresql://postgres:password@db:5432/${projectName}"\nJWT_SECRET="a_very_secret_key_change_this"`;
71
+ await fs.writeFile(path.join(projectDir, '.env'), envContent);
72
+ await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
73
+
74
+ console.log(chalk.green(' -> Python (FastAPI) backend generation is complete!'));
75
+ console.log(chalk.yellow('\nTo run your new Python backend with Docker:'));
76
+ console.log(chalk.cyan(' 1. Make sure Docker Desktop is running.'));
77
+ console.log(chalk.cyan(' 2. Run: `docker-compose up --build`'));
78
+ console.log(chalk.cyan(' 3. API will be available at http://localhost:8000 and docs at http://localhost:8000/docs'));
79
+
80
+ } catch (error) {
81
+ if (error.code === 'ENOENT') {
82
+ throw new Error(`'${error.command}' command not found. Please ensure Python and venv are installed and in your system's PATH.`);
83
+ }
84
+ throw error;
85
+ }
86
+ }
@@ -1,8 +1,12 @@
1
- const fs = require('fs-extra');
2
- const ejs = require('ejs');
3
- const path = require('path');
1
+ import fs from 'fs-extra';
2
+ import ejs from 'ejs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
 
5
- async function renderAndWrite(templatePath, outPath, data) {
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ export async function renderAndWrite(templatePath, outPath, data) {
6
10
  try {
7
11
  const tpl = await fs.readFile(templatePath, 'utf-8');
8
12
  const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
@@ -14,8 +18,6 @@ async function renderAndWrite(templatePath, outPath, data) {
14
18
  }
15
19
  }
16
20
 
17
- function getTemplatePath(subpath) {
21
+ export function getTemplatePath(subpath) {
18
22
  return path.join(__dirname, '..', 'templates', subpath);
19
- }
20
-
21
- module.exports = { renderAndWrite, getTemplatePath };
23
+ }
@@ -0,0 +1,27 @@
1
+ # Auto-generated by create-backlist (.NET Core)
2
+
3
+ # ---- Build Stage ----
4
+ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
5
+ WORKDIR /src
6
+
7
+ COPY *.csproj .
8
+ RUN dotnet restore
9
+
10
+ COPY . .
11
+ RUN dotnet publish -c Release -o /app/publish --no-restore
12
+
13
+ # ---- Runtime Stage ----
14
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0
15
+ WORKDIR /app
16
+
17
+ ENV ASPNETCORE_URLS=http://+:5000
18
+ ENV ASPNETCORE_ENVIRONMENT=Production
19
+
20
+ COPY --from=build /app/publish .
21
+
22
+ EXPOSE 5000
23
+
24
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
25
+ CMD curl -f http://localhost:5000/health || exit 1
26
+
27
+ ENTRYPOINT ["dotnet", "<%= projectName %>.dll"]
@@ -0,0 +1,33 @@
1
+ # Auto-generated by create-backlist (.NET Core)
2
+ version: '3.8'
3
+
4
+ services:
5
+ app:
6
+ build: .
7
+ ports:
8
+ - "5000:5000"
9
+ environment:
10
+ - ASPNETCORE_ENVIRONMENT=Development
11
+ - ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=<%= projectName %>;Username=postgres;Password=postgres
12
+ depends_on:
13
+ db:
14
+ condition: service_healthy
15
+
16
+ db:
17
+ image: postgres:16-alpine
18
+ environment:
19
+ POSTGRES_USER: postgres
20
+ POSTGRES_PASSWORD: postgres
21
+ POSTGRES_DB: <%= projectName %>
22
+ ports:
23
+ - "5432:5432"
24
+ volumes:
25
+ - pgdata:/var/lib/postgresql/data
26
+ healthcheck:
27
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
28
+ interval: 5s
29
+ timeout: 3s
30
+ retries: 5
31
+
32
+ volumes:
33
+ pgdata:
@@ -27,7 +27,7 @@ public class <%= controllerName %>Controller {
27
27
  }
28
28
 
29
29
  @GetMapping("/{id}")
30
- public ResponseEntity<<%= controllerName %>> one(@PathVariable String id) {
30
+ public ResponseEntity<<%= controllerName %>> one(@PathVariable Long id) {
31
31
  return service.findById(id)
32
32
  .map(ResponseEntity::ok)
33
33
  .orElse(ResponseEntity.notFound().build());
@@ -39,14 +39,14 @@ public class <%= controllerName %>Controller {
39
39
  }
40
40
 
41
41
  @PutMapping("/{id}")
42
- public ResponseEntity<<%= controllerName %>> update(@PathVariable String id, @RequestBody <%= controllerName %> m) {
42
+ public ResponseEntity<<%= controllerName %>> update(@PathVariable Long id, @RequestBody <%= controllerName %> m) {
43
43
  return service.update(id, m)
44
44
  .map(ResponseEntity::ok)
45
45
  .orElse(ResponseEntity.notFound().build());
46
46
  }
47
47
 
48
48
  @DeleteMapping("/{id}")
49
- public ResponseEntity<Void> delete(@PathVariable String id) {
49
+ public ResponseEntity<Void> delete(@PathVariable Long id) {
50
50
  return service.delete(id)
51
51
  ? ResponseEntity.noContent().build()
52
52
  : ResponseEntity.notFound().build();
@@ -0,0 +1,59 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import dotenv from 'dotenv';
4
+ import helmet from 'helmet';
5
+ import morgan from 'morgan';
6
+
7
+ dotenv.config();
8
+
9
+ const app = express();
10
+
11
+ app.use(helmet());
12
+ app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
13
+
14
+ app.use(cors({
15
+ origin: process.env.CORS_ORIGIN
16
+ ? process.env.CORS_ORIGIN.split(',').map(s => s.trim())
17
+ : ['http://localhost:3000', 'http://localhost:5173'],
18
+ credentials: true,
19
+ }));
20
+
21
+ app.use(express.json({ limit: '1mb' }));
22
+
23
+ app.get('/api/health', (req, res) => {
24
+ res.status(200).json({ status: 'ok' });
25
+ });
26
+
27
+ app.get('/', (req, res) => {
28
+ res.send('Backend is running! Generated by create-backlist.');
29
+ });
30
+
31
+ // INJECT:ROUTES
32
+
33
+ app.use((req, res) => {
34
+ res.status(404).json({ message: 'Route not found' });
35
+ });
36
+
37
+ app.use((err, req, res, _next) => {
38
+ console.error(err);
39
+ res.status(err?.statusCode || 500).json({
40
+ message: err?.message || 'Internal Server Error',
41
+ });
42
+ });
43
+
44
+ const PORT = process.env.PORT || 8000;
45
+
46
+ const server = app.listen(PORT, () => {
47
+ console.log(`Server running on http://localhost:${PORT}`);
48
+ });
49
+
50
+ function shutdown(signal) {
51
+ console.log(`Received ${signal}. Shutting down...`);
52
+ server.close(() => {
53
+ console.log('HTTP server closed.');
54
+ process.exit(0);
55
+ });
56
+ }
57
+
58
+ process.on('SIGINT', () => shutdown('SIGINT'));
59
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
@@ -0,0 +1,12 @@
1
+ FROM node:20-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json ./
6
+ RUN npm ci --only=production
7
+
8
+ COPY . .
9
+
10
+ EXPOSE <%= port %>
11
+
12
+ CMD ["node", "src/server.js"]
@@ -0,0 +1,66 @@
1
+ <% if (dbType === 'mongoose') { %>
2
+ import User from '../models/User.model.js';
3
+ <% } else { %>
4
+ import { prisma } from '../db.js';
5
+ <% } %>
6
+ import bcrypt from 'bcryptjs';
7
+ import jwt from 'jsonwebtoken';
8
+
9
+ const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
10
+ const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '5h';
11
+
12
+ export class AuthController {
13
+
14
+ static async register(req, res) {
15
+ try {
16
+ const { name, email, password } = req.body;
17
+ if (!email || !password) {
18
+ return res.status(400).json({ message: 'Email and password are required' });
19
+ }
20
+
21
+ const hashedPassword = await bcrypt.hash(password, 12);
22
+
23
+ <% if (dbType === 'mongoose') { %>
24
+ const existing = await User.findOne({ email });
25
+ if (existing) return res.status(409).json({ message: 'Email already in use' });
26
+
27
+ const user = await User.create({ name, email, password: hashedPassword });
28
+ const token = jwt.sign({ id: user._id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
29
+ <% } else { %>
30
+ const existing = await prisma.user.findUnique({ where: { email } });
31
+ if (existing) return res.status(409).json({ message: 'Email already in use' });
32
+
33
+ const user = await prisma.user.create({ data: { name, email, password: hashedPassword } });
34
+ const token = jwt.sign({ id: user.id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
35
+ <% } %>
36
+
37
+ res.status(201).json({ token, user: { id: user.id || user._id, name: user.name, email: user.email } });
38
+ } catch (error) {
39
+ res.status(500).json({ message: error.message });
40
+ }
41
+ }
42
+
43
+ static async login(req, res) {
44
+ try {
45
+ const { email, password } = req.body;
46
+ if (!email || !password) {
47
+ return res.status(400).json({ message: 'Email and password are required' });
48
+ }
49
+
50
+ <% if (dbType === 'mongoose') { %>
51
+ const user = await User.findOne({ email });
52
+ <% } else { %>
53
+ const user = await prisma.user.findUnique({ where: { email } });
54
+ <% } %>
55
+ if (!user) return res.status(401).json({ message: 'Invalid credentials' });
56
+
57
+ const isMatch = await bcrypt.compare(password, user.password);
58
+ if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' });
59
+
60
+ const token = jwt.sign({ id: user.id || user._id, email: user.email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
61
+ res.status(200).json({ token, user: { id: user.id || user._id, name: user.name, email: user.email } });
62
+ } catch (error) {
63
+ res.status(500).json({ message: error.message });
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,19 @@
1
+ import jwt from 'jsonwebtoken';
2
+
3
+ const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
4
+
5
+ export function authMiddleware(req, res, next) {
6
+ const authHeader = req.headers.authorization;
7
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
8
+ return res.status(401).json({ message: 'Access denied. No token provided.' });
9
+ }
10
+
11
+ const token = authHeader.split(' ')[1];
12
+ try {
13
+ const decoded = jwt.verify(token, JWT_SECRET);
14
+ req.user = decoded;
15
+ next();
16
+ } catch (error) {
17
+ return res.status(401).json({ message: 'Invalid or expired token.' });
18
+ }
19
+ }
@@ -0,0 +1,9 @@
1
+ import { Router } from 'express';
2
+ import { AuthController } from '../controllers/Auth.controller.js';
3
+
4
+ const router = Router();
5
+
6
+ router.post('/register', AuthController.register);
7
+ router.post('/login', AuthController.login);
8
+
9
+ export default router;