create-backlist 7.0.1 → 7.3.1

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 (54) hide show
  1. package/README.md +1 -10
  2. package/bin/index.js +242 -275
  3. package/package.json +3 -2
  4. package/src/ai-agent.js +171 -171
  5. package/src/analyzer.js +750 -495
  6. package/src/env-resolver.js +70 -0
  7. package/src/generators/dotnet.js +134 -133
  8. package/src/generators/java.js +248 -233
  9. package/src/generators/js.js +346 -0
  10. package/src/generators/nestjs.js +278 -0
  11. package/src/generators/node.js +404 -404
  12. package/src/generators/python.js +86 -104
  13. package/src/generators/template.js +22 -22
  14. package/src/project-detector.js +131 -0
  15. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
  16. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
  17. package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
  18. package/src/templates/js-express/base/server.js +59 -0
  19. package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
  20. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
  21. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
  22. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
  23. package/src/templates/js-express/partials/controller.js.ejs +53 -0
  24. package/src/templates/js-express/partials/db.js.ejs +19 -0
  25. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
  26. package/src/templates/js-express/partials/model.js.ejs +18 -0
  27. package/src/templates/js-express/partials/package.json.ejs +17 -0
  28. package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
  29. package/src/templates/js-express/partials/routes.js.ejs +19 -0
  30. package/src/templates/js-express/partials/seeder.js.ejs +103 -0
  31. package/src/templates/js-express/partials/service.js.ejs +51 -0
  32. package/src/templates/js-express/partials/swagger.js.ejs +30 -0
  33. package/src/templates/js-express/partials/test.js.ejs +46 -0
  34. package/src/templates/nestjs/base/app.module.ts +9 -0
  35. package/src/templates/nestjs/base/main.ts +23 -0
  36. package/src/templates/nestjs/base/tsconfig.json +21 -0
  37. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
  38. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
  39. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
  40. package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
  41. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
  42. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
  43. package/src/templates/nestjs/partials/module.ts.ejs +10 -0
  44. package/src/templates/nestjs/partials/package.json.ejs +27 -0
  45. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
  46. package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
  47. package/src/templates/nestjs/partials/service.ts.ejs +67 -0
  48. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
  49. package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -56
  50. package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -26
  51. package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -27
  52. package/src/utils.js +11 -11
  53. /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
  54. /package/src/templates/{node-ts-express → dotnet}/partials/Model.cs.ejs +0 -0
@@ -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,23 +1,23 @@
1
- import fs from 'fs-extra';
2
- import ejs from 'ejs';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
-
9
- export async function renderAndWrite(templatePath, outPath, data) {
10
- try {
11
- const tpl = await fs.readFile(templatePath, 'utf-8');
12
- const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
13
- await fs.outputFile(outPath, code);
14
- } catch (err) {
15
- console.error('EJS render failed for:', templatePath);
16
- console.error('Data keys:', Object.keys(data || {}));
17
- throw err;
18
- }
19
- }
20
-
21
- export function getTemplatePath(subpath) {
22
- return path.join(__dirname, '..', 'templates', subpath);
1
+ import fs from 'fs-extra';
2
+ import ejs from 'ejs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ export async function renderAndWrite(templatePath, outPath, data) {
10
+ try {
11
+ const tpl = await fs.readFile(templatePath, 'utf-8');
12
+ const code = ejs.render(tpl, data || {}, { filename: templatePath }); // filename helps with EJS errors
13
+ await fs.outputFile(outPath, code);
14
+ } catch (err) {
15
+ console.error('EJS render failed for:', templatePath);
16
+ console.error('Data keys:', Object.keys(data || {}));
17
+ throw err;
18
+ }
19
+ }
20
+
21
+ export function getTemplatePath(subpath) {
22
+ return path.join(__dirname, '..', 'templates', subpath);
23
23
  }
@@ -0,0 +1,131 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+
4
+ const NEXTJS_SCAN_DIRS = [
5
+ "app",
6
+ "pages",
7
+ "src",
8
+ "components",
9
+ "lib",
10
+ "hooks",
11
+ "services",
12
+ "utils",
13
+ "features",
14
+ "modules",
15
+ ];
16
+
17
+ const REACT_SCAN_DIRS = ["src"];
18
+
19
+ export async function detectProjectType(projectRoot) {
20
+ const result = {
21
+ type: "unknown",
22
+ scanDirs: [],
23
+ apiRouteDirs: [],
24
+ framework: "unknown",
25
+ };
26
+
27
+ const pkgPath = path.join(projectRoot, "package.json");
28
+ let pkg = null;
29
+ if (await fs.pathExists(pkgPath)) {
30
+ try {
31
+ pkg = await fs.readJson(pkgPath);
32
+ } catch {}
33
+ }
34
+
35
+ const deps = pkg
36
+ ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
37
+ : {};
38
+
39
+ const hasNext = !!deps.next || (await hasNextConfig(projectRoot));
40
+ const hasReact = !!deps.react;
41
+ const hasVite = !!deps.vite;
42
+
43
+ const hasAppDir = await fs.pathExists(path.join(projectRoot, "app"));
44
+ const hasPagesDir = await fs.pathExists(path.join(projectRoot, "pages"));
45
+ const hasSrcApp = await fs.pathExists(path.join(projectRoot, "src", "app"));
46
+ const hasSrcPages = await fs.pathExists(
47
+ path.join(projectRoot, "src", "pages")
48
+ );
49
+
50
+ if (hasNext) {
51
+ result.framework = "next";
52
+
53
+ if ((hasAppDir || hasSrcApp) && (hasPagesDir || hasSrcPages)) {
54
+ result.type = "nextjs-hybrid";
55
+ } else if (hasAppDir || hasSrcApp) {
56
+ result.type = "nextjs-app";
57
+ } else if (hasPagesDir || hasSrcPages) {
58
+ result.type = "nextjs-pages";
59
+ } else {
60
+ result.type = "nextjs-app";
61
+ }
62
+
63
+ const candidateDirs = [...NEXTJS_SCAN_DIRS];
64
+ for (const dir of candidateDirs) {
65
+ const abs = path.join(projectRoot, dir);
66
+ if (await fs.pathExists(abs)) {
67
+ result.scanDirs.push(abs);
68
+ }
69
+ const srcNested = path.join(projectRoot, "src", dir);
70
+ if (dir !== "src" && (await fs.pathExists(srcNested))) {
71
+ result.scanDirs.push(srcNested);
72
+ }
73
+ }
74
+
75
+ if (hasAppDir) {
76
+ const appApiDir = path.join(projectRoot, "app", "api");
77
+ if (await fs.pathExists(appApiDir)) result.apiRouteDirs.push(appApiDir);
78
+ result.apiRouteDirs.push(path.join(projectRoot, "app"));
79
+ }
80
+ if (hasSrcApp) {
81
+ const srcAppApiDir = path.join(projectRoot, "src", "app", "api");
82
+ if (await fs.pathExists(srcAppApiDir))
83
+ result.apiRouteDirs.push(srcAppApiDir);
84
+ result.apiRouteDirs.push(path.join(projectRoot, "src", "app"));
85
+ }
86
+ if (hasPagesDir) {
87
+ const pagesApiDir = path.join(projectRoot, "pages", "api");
88
+ if (await fs.pathExists(pagesApiDir))
89
+ result.apiRouteDirs.push(pagesApiDir);
90
+ }
91
+ if (hasSrcPages) {
92
+ const srcPagesApiDir = path.join(projectRoot, "src", "pages", "api");
93
+ if (await fs.pathExists(srcPagesApiDir))
94
+ result.apiRouteDirs.push(srcPagesApiDir);
95
+ }
96
+ } else if (hasReact) {
97
+ result.framework = hasVite ? "vite" : "react";
98
+ result.type = hasVite ? "vite-react" : "react";
99
+
100
+ for (const dir of REACT_SCAN_DIRS) {
101
+ const abs = path.join(projectRoot, dir);
102
+ if (await fs.pathExists(abs)) {
103
+ result.scanDirs.push(abs);
104
+ }
105
+ }
106
+ }
107
+
108
+ if (result.scanDirs.length === 0) {
109
+ const fallback = path.join(projectRoot, "src");
110
+ if (await fs.pathExists(fallback)) {
111
+ result.scanDirs.push(fallback);
112
+ }
113
+ }
114
+
115
+ result.scanDirs = [...new Set(result.scanDirs)];
116
+ result.apiRouteDirs = [...new Set(result.apiRouteDirs)];
117
+
118
+ return result;
119
+ }
120
+
121
+ async function hasNextConfig(projectRoot) {
122
+ const configs = [
123
+ "next.config.js",
124
+ "next.config.mjs",
125
+ "next.config.ts",
126
+ ];
127
+ for (const cfg of configs) {
128
+ if (await fs.pathExists(path.join(projectRoot, cfg))) return true;
129
+ }
130
+ return false;
131
+ }
@@ -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;