@alexmc2/create-express-api-starter 0.1.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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/cli.js +930 -0
  4. package/dist/cli.js.map +1 -0
  5. package/package.json +70 -0
  6. package/templates/js/mvc/.env.example.ejs +7 -0
  7. package/templates/js/mvc/.eslintrc.cjs.ejs +24 -0
  8. package/templates/js/mvc/.gitignore.ejs +6 -0
  9. package/templates/js/mvc/README.md.ejs +187 -0
  10. package/templates/js/mvc/__tests__/app.test.js.ejs +51 -0
  11. package/templates/js/mvc/compose.yaml.ejs +13 -0
  12. package/templates/js/mvc/db/schema.sql.ejs +8 -0
  13. package/templates/js/mvc/db/seed.sql.ejs +7 -0
  14. package/templates/js/mvc/jest.config.js.ejs +6 -0
  15. package/templates/js/mvc/package.json.ejs +40 -0
  16. package/templates/js/mvc/scripts/dbCreate.js.ejs +97 -0
  17. package/templates/js/mvc/scripts/dbReset.js.ejs +42 -0
  18. package/templates/js/mvc/scripts/dbSeed.js.ejs +69 -0
  19. package/templates/js/mvc/scripts/dbSetup.js.ejs +69 -0
  20. package/templates/js/mvc/src/app.js.ejs +57 -0
  21. package/templates/js/mvc/src/controllers/usersController.js.ejs +32 -0
  22. package/templates/js/mvc/src/db/pool.js.ejs +19 -0
  23. package/templates/js/mvc/src/errors/AppError.js.ejs +16 -0
  24. package/templates/js/mvc/src/middleware/errorHandler.js.ejs +39 -0
  25. package/templates/js/mvc/src/middleware/notFound.js.ejs +15 -0
  26. package/templates/js/mvc/src/repositories/usersRepository.js.ejs +69 -0
  27. package/templates/js/mvc/src/routes/health.js.ejs +19 -0
  28. package/templates/js/mvc/src/routes/users.js.ejs +22 -0
  29. package/templates/js/mvc/src/server.js.ejs +21 -0
  30. package/templates/js/mvc/src/services/usersService.js.ejs +34 -0
  31. package/templates/js/mvc/src/utils/getPort.js.ejs +18 -0
  32. package/templates/js/simple/.env.example.ejs +7 -0
  33. package/templates/js/simple/.eslintrc.cjs.ejs +24 -0
  34. package/templates/js/simple/.gitignore.ejs +6 -0
  35. package/templates/js/simple/README.md.ejs +182 -0
  36. package/templates/js/simple/__tests__/app.test.js.ejs +51 -0
  37. package/templates/js/simple/compose.yaml.ejs +13 -0
  38. package/templates/js/simple/db/schema.sql.ejs +8 -0
  39. package/templates/js/simple/db/seed.sql.ejs +7 -0
  40. package/templates/js/simple/jest.config.js.ejs +6 -0
  41. package/templates/js/simple/package.json.ejs +40 -0
  42. package/templates/js/simple/scripts/dbCreate.js.ejs +97 -0
  43. package/templates/js/simple/scripts/dbReset.js.ejs +42 -0
  44. package/templates/js/simple/scripts/dbSeed.js.ejs +69 -0
  45. package/templates/js/simple/scripts/dbSetup.js.ejs +69 -0
  46. package/templates/js/simple/src/app.js.ejs +57 -0
  47. package/templates/js/simple/src/db/pool.js.ejs +19 -0
  48. package/templates/js/simple/src/errors/AppError.js.ejs +16 -0
  49. package/templates/js/simple/src/middleware/errorHandler.js.ejs +39 -0
  50. package/templates/js/simple/src/middleware/notFound.js.ejs +15 -0
  51. package/templates/js/simple/src/repositories/usersRepository.js.ejs +69 -0
  52. package/templates/js/simple/src/routes/health.js.ejs +19 -0
  53. package/templates/js/simple/src/routes/users.js.ejs +52 -0
  54. package/templates/js/simple/src/server.js.ejs +21 -0
  55. package/templates/js/simple/src/utils/getPort.js.ejs +18 -0
  56. package/templates/ts/mvc/.env.example.ejs +7 -0
  57. package/templates/ts/mvc/.eslintrc.cjs.ejs +27 -0
  58. package/templates/ts/mvc/.gitignore.ejs +6 -0
  59. package/templates/ts/mvc/README.md.ejs +188 -0
  60. package/templates/ts/mvc/__tests__/app.test.ts.ejs +45 -0
  61. package/templates/ts/mvc/compose.yaml.ejs +13 -0
  62. package/templates/ts/mvc/db/schema.sql.ejs +8 -0
  63. package/templates/ts/mvc/db/seed.sql.ejs +7 -0
  64. package/templates/ts/mvc/jest.config.js.ejs +7 -0
  65. package/templates/ts/mvc/package.json.ejs +51 -0
  66. package/templates/ts/mvc/scripts/dbCreate.js.ejs +93 -0
  67. package/templates/ts/mvc/scripts/dbReset.js.ejs +40 -0
  68. package/templates/ts/mvc/scripts/dbSeed.js.ejs +62 -0
  69. package/templates/ts/mvc/scripts/dbSetup.js.ejs +62 -0
  70. package/templates/ts/mvc/src/app.ts.ejs +45 -0
  71. package/templates/ts/mvc/src/controllers/usersController.ts.ejs +31 -0
  72. package/templates/ts/mvc/src/db/pool.ts.ejs +17 -0
  73. package/templates/ts/mvc/src/errors/AppError.ts.ejs +14 -0
  74. package/templates/ts/mvc/src/middleware/errorHandler.ts.ejs +49 -0
  75. package/templates/ts/mvc/src/middleware/notFound.ts.ejs +13 -0
  76. package/templates/ts/mvc/src/repositories/usersRepository.ts.ejs +87 -0
  77. package/templates/ts/mvc/src/routes/health.ts.ejs +13 -0
  78. package/templates/ts/mvc/src/routes/users.ts.ejs +14 -0
  79. package/templates/ts/mvc/src/server.ts.ejs +15 -0
  80. package/templates/ts/mvc/src/services/usersService.ts.ejs +35 -0
  81. package/templates/ts/mvc/src/utils/getPort.ts.ejs +12 -0
  82. package/templates/ts/mvc/tsconfig.json.ejs +13 -0
  83. package/templates/ts/simple/.env.example.ejs +7 -0
  84. package/templates/ts/simple/.eslintrc.cjs.ejs +27 -0
  85. package/templates/ts/simple/.gitignore.ejs +6 -0
  86. package/templates/ts/simple/README.md.ejs +182 -0
  87. package/templates/ts/simple/__tests__/app.test.ts.ejs +45 -0
  88. package/templates/ts/simple/compose.yaml.ejs +13 -0
  89. package/templates/ts/simple/db/schema.sql.ejs +8 -0
  90. package/templates/ts/simple/db/seed.sql.ejs +7 -0
  91. package/templates/ts/simple/jest.config.js.ejs +7 -0
  92. package/templates/ts/simple/package.json.ejs +51 -0
  93. package/templates/ts/simple/scripts/dbCreate.js.ejs +93 -0
  94. package/templates/ts/simple/scripts/dbReset.js.ejs +40 -0
  95. package/templates/ts/simple/scripts/dbSeed.js.ejs +62 -0
  96. package/templates/ts/simple/scripts/dbSetup.js.ejs +62 -0
  97. package/templates/ts/simple/src/app.ts.ejs +45 -0
  98. package/templates/ts/simple/src/db/pool.ts.ejs +17 -0
  99. package/templates/ts/simple/src/errors/AppError.ts.ejs +14 -0
  100. package/templates/ts/simple/src/middleware/errorHandler.ts.ejs +49 -0
  101. package/templates/ts/simple/src/middleware/notFound.ts.ejs +13 -0
  102. package/templates/ts/simple/src/repositories/usersRepository.ts.ejs +87 -0
  103. package/templates/ts/simple/src/routes/health.ts.ejs +13 -0
  104. package/templates/ts/simple/src/routes/users.ts.ejs +43 -0
  105. package/templates/ts/simple/src/server.ts.ejs +15 -0
  106. package/templates/ts/simple/src/utils/getPort.ts.ejs +12 -0
  107. package/templates/ts/simple/tsconfig.json.ejs +13 -0
@@ -0,0 +1,49 @@
1
+ <% if (educational) { %>// File overview: Converts thrown errors into consistent JSON HTTP responses for clients.
2
+ <% } %>
3
+ import type { ErrorRequestHandler } from 'express';
4
+
5
+ interface HttpError extends Error {
6
+ status?: number;
7
+ code?: string;
8
+ detail?: string;
9
+ }
10
+
11
+ <% if (educational) { %>// Express treats middleware with four parameters as an error handler.
12
+ // Any error passed to next(error) in the app is routed to this function.
13
+ <% } %>const errorHandler: ErrorRequestHandler = (error: HttpError, _req, res, _next) => {
14
+ <% if (educational) { %> // PostgreSQL error code 23505 means a UNIQUE constraint was violated.
15
+ // Returning HTTP 409 tells clients that the request conflicts with existing data.
16
+ <% } %> if (error?.code === '23505') {
17
+ const detail = typeof error?.detail === 'string' ? error.detail : '';
18
+ const message = detail.includes('email')
19
+ ? 'A user with this email already exists.'
20
+ : 'A record with this value already exists.';
21
+
22
+ return res.status(409).json({
23
+ status: 409,
24
+ message
25
+ });
26
+ }
27
+
28
+ const status = Number.isInteger(error?.status) ? (error.status as number) : 500;
29
+ const message = typeof error?.message === 'string' ? error.message : 'Internal server error.';
30
+
31
+ const payload: {
32
+ status: number;
33
+ message: string;
34
+ stack?: string;
35
+ } = {
36
+ status,
37
+ message
38
+ };
39
+
40
+ <% if (educational) { %> // Include stack traces in development to speed up debugging.
41
+ // Omit them in production so internal implementation details stay private.
42
+ <% } %> if (process.env.NODE_ENV === 'development' && typeof error?.stack === 'string') {
43
+ payload.stack = error.stack;
44
+ }
45
+
46
+ res.status(status).json(payload);
47
+ };
48
+
49
+ export default errorHandler;
@@ -0,0 +1,13 @@
1
+ <% if (educational) { %>// File overview: Handles unmatched routes by forwarding a 404 error to the central error handler.
2
+ <% } %>
3
+ import type { RequestHandler } from 'express';
4
+
5
+ import AppError from '../errors/AppError';
6
+
7
+ <% if (educational) { %>// This middleware runs only when no route matched the request.
8
+ // It forwards a 404 error to the central error handler for a uniform response.
9
+ <% } %>const notFound: RequestHandler = (_req, _res, next) => {
10
+ next(new AppError(404, 'Route not found.'));
11
+ };
12
+
13
+ export default notFound;
@@ -0,0 +1,87 @@
1
+ <% if (educational) { %>// File overview: Encapsulates user data access so route code stays focused on HTTP concerns.
2
+ <% } %>
3
+ <% if (isPostgres) { %><% if (educational) { %>// Keep SQL statements in the repository so HTTP layers stay focused on requests
4
+ // and responses. This separation makes the code easier to test and maintain.
5
+ <% } %>import pool from '../db/pool';
6
+
7
+ interface UserRow {
8
+ id: number;
9
+ name: string;
10
+ email: string | null;
11
+ }
12
+
13
+ interface CreateUserInput {
14
+ name: string;
15
+ email: string | null;
16
+ }
17
+
18
+ async function getAll(): Promise<UserRow[]> {
19
+ const result = await pool.query<UserRow>('SELECT id, name, email FROM users ORDER BY id ASC');
20
+ return result.rows;
21
+ }
22
+
23
+ async function create({ name, email }: CreateUserInput): Promise<UserRow> {
24
+ <% if (educational) { %> // Use parameter placeholders ($1, $2) so pg binds values safely.
25
+ <% } %> const result = await pool.query<UserRow>(
26
+ 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email',
27
+ [name, email]
28
+ );
29
+
30
+ return result.rows[0];
31
+ }
32
+
33
+ export default {
34
+ getAll,
35
+ create
36
+ };
37
+ <% } else { %><% if (educational) { %>// Keep data access in one place so the rest of the app does not depend on
38
+ // storage details. This in-memory version can be replaced later with a real
39
+ // database by changing this file only.
40
+ <% } %>export interface User {
41
+ id: number;
42
+ name: string;
43
+ email: string | null;
44
+ }
45
+
46
+ interface CreateUserInput {
47
+ name: string;
48
+ email: string | null;
49
+ }
50
+
51
+ const users: User[] = [
52
+ {
53
+ id: 1,
54
+ name: 'Ada Lovelace',
55
+ email: 'ada@example.com'
56
+ },
57
+ {
58
+ id: 2,
59
+ name: 'Grace Hopper',
60
+ email: 'grace@example.com'
61
+ }
62
+ ];
63
+
64
+ let nextId = users.length + 1;
65
+
66
+ async function getAll(): Promise<User[]> {
67
+ return users;
68
+ }
69
+
70
+ async function create({ name, email }: CreateUserInput): Promise<User> {
71
+ const user: User = {
72
+ id: nextId,
73
+ name,
74
+ email
75
+ };
76
+
77
+ nextId += 1;
78
+ users.push(user);
79
+
80
+ return user;
81
+ }
82
+
83
+ export default {
84
+ getAll,
85
+ create
86
+ };
87
+ <% } %>
@@ -0,0 +1,13 @@
1
+ <% if (educational) { %>// File overview: Registers the health-check route used to confirm the API process is running.
2
+ <% } %>
3
+ import { Router } from 'express';
4
+
5
+ const router = Router();
6
+
7
+ <% if (educational) { %>// Health-check endpoint used by monitors, load balancers, and container
8
+ // platforms to verify that the API process is running.
9
+ <% } %>router.get('/', (_req, res) => {
10
+ res.status(200).json({ ok: true });
11
+ });
12
+
13
+ export default router;
@@ -0,0 +1,43 @@
1
+ <% if (educational) { %>// File overview: Defines /api/users routes, including request validation and repository calls.
2
+ <% } %>
3
+ import { Router } from 'express';
4
+
5
+ import usersRepository from '../repositories/usersRepository';
6
+ import AppError from '../errors/AppError';
7
+
8
+ const router = Router();
9
+
10
+ <% if (educational) { %>// Async route handlers should pass failures to next(error) so the
11
+ // centralized error handler can return a consistent HTTP response.
12
+ <% } %>router.get('/', async (_req, res, next) => {
13
+ try {
14
+ const users = await usersRepository.getAll();
15
+ res.status(200).json(users);
16
+ } catch (error) {
17
+ next(error);
18
+ }
19
+ });
20
+
21
+ router.post('/', async (req, res, next) => {
22
+ try {
23
+ <% if (educational) { %> // Validate input at the API boundary before storing data.
24
+ // Calling next(error) skips the rest of the handler and moves control
25
+ // to the central error middleware, which formats the HTTP response.
26
+ <% } %> const { name, email } = req.body ?? {};
27
+
28
+ if (!name || typeof name !== 'string') {
29
+ return next(new AppError(400, '"name" is required.'));
30
+ }
31
+
32
+ const user = await usersRepository.create({
33
+ name: name.trim(),
34
+ email: typeof email === 'string' ? email.trim() : null
35
+ });
36
+
37
+ return res.status(201).json(user);
38
+ } catch (error) {
39
+ return next(error);
40
+ }
41
+ });
42
+
43
+ export default router;
@@ -0,0 +1,15 @@
1
+ <% if (educational) { %>// File overview: Application entry point that loads environment variables and starts the HTTP server.
2
+ <% } %>
3
+ <% if (educational) { %>// Load variables from .env before other modules read process.env values.
4
+ <% } %>import 'dotenv/config';
5
+
6
+ import app from './app';
7
+ import { getPort } from './utils/getPort';
8
+
9
+ <% if (educational) { %>// Read the server port from PORT, defaulting to 3000 for local development.
10
+ <% } %>const port = getPort(process.env.PORT, 3000);
11
+
12
+ <% if (educational) { %>// Start the HTTP server and listen for incoming requests.
13
+ <% } %>app.listen(port, () => {
14
+ console.log(`Server listening on port ${port}`);
15
+ });
@@ -0,0 +1,12 @@
1
+ <% if (educational) { %>// File overview: Parses PORT into a usable integer with a safe fallback.
2
+ <% } %>
3
+ <% if (educational) { %>// Keep this helper small and pure so entrypoint code stays easy to scan.
4
+ <% } %>export function getPort(value: string | undefined, fallback = 3000): number {
5
+ const parsed = Number(value);
6
+
7
+ if (Number.isInteger(parsed) && parsed > 0) {
8
+ return parsed;
9
+ }
10
+
11
+ return fallback;
12
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "node16",
4
+ "moduleResolution": "node16",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "esModuleInterop": true,
8
+ "strict": true,
9
+ "target": "ES2022",
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }