@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,42 @@
1
+ <% if (educational) { %>// File overview: Executes the reset workflow by running setup and seed scripts in order.
2
+ <% } %>
3
+ <% if (isEsm) { %>import { spawn } from 'node:child_process';
4
+ <% } else { %>const { spawn } = require('node:child_process');
5
+ <% } %>
6
+
7
+ <% if (educational) { %>// Run an npm script and stream output to this terminal.
8
+ // Wrapping spawn in a Promise lets us await each step in order.
9
+ <% } %>function run(command, args) {
10
+ return new Promise((resolve, reject) => {
11
+ const child = spawn(command, args, {
12
+ stdio: 'inherit',
13
+ shell: true
14
+ });
15
+
16
+ child.on('exit', (code) => {
17
+ if (code === 0) {
18
+ resolve();
19
+ return;
20
+ }
21
+
22
+ reject(new Error(`${command} ${args.join(' ')} failed with exit code ${code}`));
23
+ });
24
+
25
+ child.on('error', reject);
26
+ });
27
+ }
28
+
29
+ async function main() {
30
+ <% if (isDocker) { %><% if (educational) { %> // Restart the container so reset starts from a clean database state.
31
+ <% } %> await run('npm', ['run', 'db:down']);
32
+ await run('npm', ['run', 'db:up']);
33
+ <% } %><% if (educational) { %> // Recreate tables first, then insert the sample data.
34
+ <% } %> await run('npm', ['run', 'db:setup']);
35
+ await run('npm', ['run', 'db:seed']);
36
+ }
37
+
38
+ main().catch((error) => {
39
+ <% if (educational) { %> // Child scripts print detailed logs, so this is a short summary.
40
+ <% } %> console.error(error.message);
41
+ process.exit(1);
42
+ });
@@ -0,0 +1,69 @@
1
+ <% if (educational) { %>// File overview: Runs db/seed.sql to insert starter rows into the database.
2
+ <% } %>
3
+ <% if (isEsm) { %>import 'dotenv/config';
4
+
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+
8
+ import { Pool } from 'pg';
9
+ <% } else { %>require('dotenv').config();
10
+
11
+ const fs = require('node:fs/promises');
12
+ const path = require('node:path');
13
+ const { Pool } = require('pg');
14
+ <% } %>
15
+ <% if (isDocker) { %>
16
+ const RETRIES = 20;
17
+ const RETRY_DELAY_MS = 1000;
18
+
19
+ <% if (educational) { %>// Sleep helper used between retry attempts.
20
+ <% } %>function sleep(ms) {
21
+ return new Promise((resolve) => setTimeout(resolve, ms));
22
+ }
23
+
24
+ <% if (educational) { %>// Retry a simple query until PostgreSQL is ready.
25
+ <% } %>async function waitForDatabase(pool) {
26
+ for (let attempt = 1; attempt <= RETRIES; attempt += 1) {
27
+ try {
28
+ await pool.query('SELECT 1');
29
+ return;
30
+ } catch {
31
+ console.log('Waiting for database...');
32
+ await sleep(RETRY_DELAY_MS);
33
+ }
34
+ }
35
+
36
+ throw new Error('Unable to connect to database after 20 retries.');
37
+ }
38
+ <% } %>
39
+ <% if (educational) { %>// Read db/seed.sql and run it to insert starter data.
40
+ <% } %>async function run() {
41
+ const databaseUrl = process.env.DATABASE_URL;
42
+
43
+ if (!databaseUrl) {
44
+ console.error('Error: DATABASE_URL is not set.');
45
+ console.error('Make sure you have a .env file with DATABASE_URL defined.');
46
+ console.error('You can copy .env.example to .env to get started.');
47
+ process.exit(1);
48
+ }
49
+
50
+ const pool = new Pool({ connectionString: databaseUrl });
51
+
52
+ try {
53
+ <% if (isDocker) { %><% if (educational) { %> // Docker containers may need a few seconds before accepting connections.
54
+ <% } %> await waitForDatabase(pool);
55
+ <% } %><% if (educational) { %> // Loading SQL from a file keeps sample data updates easy to review.
56
+ <% } %> const seedPath = path.join(process.cwd(), 'db', 'seed.sql');
57
+ const seedSql = await fs.readFile(seedPath, 'utf8');
58
+ await pool.query(seedSql);
59
+ console.log('Database seed applied.');
60
+ } finally {
61
+ await pool.end();
62
+ }
63
+ }
64
+
65
+ run().catch((error) => {
66
+ <% if (educational) { %> // Exit with a non-zero status so npm reports the script as failed.
67
+ <% } %> console.error(error.message);
68
+ process.exit(1);
69
+ });
@@ -0,0 +1,69 @@
1
+ <% if (educational) { %>// File overview: Runs db/schema.sql to create or reset database tables.
2
+ <% } %>
3
+ <% if (isEsm) { %>import 'dotenv/config';
4
+
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+
8
+ import { Pool } from 'pg';
9
+ <% } else { %>require('dotenv').config();
10
+
11
+ const fs = require('node:fs/promises');
12
+ const path = require('node:path');
13
+ const { Pool } = require('pg');
14
+ <% } %>
15
+ <% if (isDocker) { %>
16
+ const RETRIES = 20;
17
+ const RETRY_DELAY_MS = 1000;
18
+
19
+ <% if (educational) { %>// Sleep helper used between retry attempts while the container starts.
20
+ <% } %>function sleep(ms) {
21
+ return new Promise((resolve) => setTimeout(resolve, ms));
22
+ }
23
+
24
+ <% if (educational) { %>// Retry a simple query until PostgreSQL is ready to accept commands.
25
+ <% } %>async function waitForDatabase(pool) {
26
+ for (let attempt = 1; attempt <= RETRIES; attempt += 1) {
27
+ try {
28
+ await pool.query('SELECT 1');
29
+ return;
30
+ } catch {
31
+ console.log('Waiting for database...');
32
+ await sleep(RETRY_DELAY_MS);
33
+ }
34
+ }
35
+
36
+ throw new Error('Unable to connect to database after 20 retries.');
37
+ }
38
+ <% } %>
39
+ <% if (educational) { %>// Read db/schema.sql and apply it to the configured database.
40
+ <% } %>async function run() {
41
+ const databaseUrl = process.env.DATABASE_URL;
42
+
43
+ if (!databaseUrl) {
44
+ console.error('Error: DATABASE_URL is not set.');
45
+ console.error('Make sure you have a .env file with DATABASE_URL defined.');
46
+ console.error('You can copy .env.example to .env to get started.');
47
+ process.exit(1);
48
+ }
49
+
50
+ const pool = new Pool({ connectionString: databaseUrl });
51
+
52
+ try {
53
+ <% if (isDocker) { %><% if (educational) { %> // Docker containers may need a few seconds before accepting connections.
54
+ <% } %> await waitForDatabase(pool);
55
+ <% } %><% if (educational) { %> // Loading SQL from a file keeps schema changes visible in version control.
56
+ <% } %> const schemaPath = path.join(process.cwd(), 'db', 'schema.sql');
57
+ const schemaSql = await fs.readFile(schemaPath, 'utf8');
58
+ await pool.query(schemaSql);
59
+ console.log('Database schema applied.');
60
+ } finally {
61
+ await pool.end();
62
+ }
63
+ }
64
+
65
+ run().catch((error) => {
66
+ <% if (educational) { %> // Exit with a non-zero status so npm reports the script as failed.
67
+ <% } %> console.error(error.message);
68
+ process.exit(1);
69
+ });
@@ -0,0 +1,57 @@
1
+ <% if (educational) { %>// File overview: Builds and configures the Express app (middleware, routes, and error pipeline).
2
+ <% } %>
3
+ <% if (isEsm) { %>import cors from 'cors';
4
+ import express from 'express';
5
+ import helmet from 'helmet';
6
+ import morgan from 'morgan';
7
+
8
+ import errorHandler from './middleware/errorHandler.js';
9
+ import notFound from './middleware/notFound.js';
10
+ import healthRouter from './routes/health.js';
11
+ import usersRouter from './routes/users.js';
12
+ <% } else { %>const express = require('express');
13
+ const cors = require('cors');
14
+ const helmet = require('helmet');
15
+ const morgan = require('morgan');
16
+
17
+ const healthRouter = require('./routes/health');
18
+ const usersRouter = require('./routes/users');
19
+ const notFound = require('./middleware/notFound');
20
+ const errorHandler = require('./middleware/errorHandler');
21
+ <% } %>
22
+
23
+ const app = express();
24
+
25
+ <% if (educational) { %>// Parse JSON request bodies so route handlers can read data from req.body.
26
+ // Without this middleware, req.body is undefined for JSON requests.
27
+ <% } %>app.use(express.json());
28
+ <% if (educational) { %>// Enable CORS so browser clients on other origins can call this API.
29
+ <% } %>app.use(cors());
30
+ <% if (educational) { %>// Add common HTTP security headers.
31
+ // Helmet applies safe defaults that reduce exposure to common web attacks.
32
+ <% } %>app.use(helmet());
33
+ <% if (educational) { %>// Log each request in development format to make local debugging easier.
34
+ <% } %>app.use(morgan('dev'));
35
+
36
+ <% if (educational) { %>// Provide a simple root endpoint so visiting the API URL in a browser
37
+ // shows available routes instead of an immediate 404 response.
38
+ <% } %>app.get('/', (_req, res) => {
39
+ res.json({
40
+ message: 'API is running',
41
+ endpoints: {
42
+ health: 'GET /health',
43
+ users: 'GET /api/users',
44
+ createUser: 'POST /api/users'
45
+ }
46
+ });
47
+ });
48
+
49
+ app.use('/health', healthRouter);
50
+ app.use('/api/users', usersRouter);
51
+
52
+ app.use(notFound);
53
+ app.use(errorHandler);
54
+
55
+ <% if (isEsm) { %>export default app;
56
+ <% } else { %>module.exports = app;
57
+ <% } %>
@@ -0,0 +1,32 @@
1
+ <% if (educational) { %>// File overview: Handles /users HTTP requests and delegates business logic to the service layer.
2
+ <% } %>
3
+ <% if (isEsm) { %>import usersService from '../services/usersService.js';
4
+ <% } else { %>const usersService = require('../services/usersService');
5
+ <% } %>
6
+
7
+ <% if (educational) { %>// Controllers translate HTTP requests into service calls and convert service
8
+ // results back into HTTP responses. Business rules remain in the service layer.
9
+ <% } %>async function listUsers(_req, res, next) {
10
+ try {
11
+ const users = await usersService.listUsers();
12
+ res.status(200).json(users);
13
+ } catch (error) {
14
+ <% if (educational) { %> // Pass errors to next(error) so the central error handler can respond.
15
+ <% } %> next(error);
16
+ }
17
+ }
18
+
19
+ async function createUser(req, res, next) {
20
+ try {
21
+ const user = await usersService.createUser(req.body ?? {});
22
+ res.status(201).json(user);
23
+ } catch (error) {
24
+ next(error);
25
+ }
26
+ }
27
+
28
+ <% if (isEsm) { %>export default {
29
+ <% } else { %>module.exports = {
30
+ <% } %> listUsers,
31
+ createUser
32
+ };
@@ -0,0 +1,19 @@
1
+ <% if (educational) { %>// File overview: Creates and exports a shared PostgreSQL connection pool for repository queries.
2
+ <% } %>
3
+ <% if (educational) { %>// Create one shared connection pool for the whole application.
4
+ // Reusing connections is faster and avoids exhausting database limits.
5
+ <% } %><% if (isEsm) { %>import { Pool } from 'pg';
6
+ <% } else { %>const { Pool } = require('pg');
7
+ <% } %>
8
+
9
+ if (!process.env.DATABASE_URL) {
10
+ throw new Error('DATABASE_URL is required for Postgres mode.');
11
+ }
12
+
13
+ const pool = new Pool({
14
+ connectionString: process.env.DATABASE_URL
15
+ });
16
+
17
+ <% if (isEsm) { %>export default pool;
18
+ <% } else { %>module.exports = pool;
19
+ <% } %>
@@ -0,0 +1,16 @@
1
+ <% if (educational) { %>// File overview: Defines an AppError class for expected API errors that include HTTP status codes.
2
+ <% } %>
3
+ <% if (educational) { %>// A custom error class for handling API errors. By extending JavaScript's
4
+ // built-in Error class, we ensure that stack traces are generated correctly
5
+ // and that the error handler can distinguish between anticipated API
6
+ // errors and unexpected application failures.
7
+ <% } %>class AppError extends Error {
8
+ constructor(status, message) {
9
+ super(message);
10
+ this.status = status;
11
+ }
12
+ }
13
+
14
+ <% if (isEsm) { %>export default AppError;
15
+ <% } else { %>module.exports = AppError;
16
+ <% } %>
@@ -0,0 +1,39 @@
1
+ <% if (educational) { %>// File overview: Converts thrown errors into consistent JSON HTTP responses for clients.
2
+ <% } %>
3
+ <% if (educational) { %>// Express treats middleware with four parameters as an error handler.
4
+ // Any error passed to next(error) in the app is routed to this function.
5
+ <% } %>function errorHandler(error, _req, res, _next) {
6
+ <% if (educational) { %> // PostgreSQL error code 23505 means a UNIQUE constraint was violated.
7
+ // Returning HTTP 409 tells clients that the request conflicts with existing data.
8
+ <% } %> if (error?.code === '23505') {
9
+ const detail = typeof error?.detail === 'string' ? error.detail : '';
10
+ const message = detail.includes('email')
11
+ ? 'A user with this email already exists.'
12
+ : 'A record with this value already exists.';
13
+
14
+ return res.status(409).json({
15
+ status: 409,
16
+ message
17
+ });
18
+ }
19
+
20
+ const status = Number.isInteger(error?.status) ? error.status : 500;
21
+ const message = typeof error?.message === 'string' ? error.message : 'Internal server error.';
22
+
23
+ const payload = {
24
+ status,
25
+ message
26
+ };
27
+
28
+ <% if (educational) { %> // Include stack traces in development to speed up debugging.
29
+ // Omit them in production so internal implementation details stay private.
30
+ <% } %> if (process.env.NODE_ENV === 'development' && typeof error?.stack === 'string') {
31
+ payload.stack = error.stack;
32
+ }
33
+
34
+ res.status(status).json(payload);
35
+ }
36
+
37
+ <% if (isEsm) { %>export default errorHandler;
38
+ <% } else { %>module.exports = errorHandler;
39
+ <% } %>
@@ -0,0 +1,15 @@
1
+ <% if (educational) { %>// File overview: Handles unmatched routes by forwarding a 404 error to the central error handler.
2
+ <% } %>
3
+ <% if (isEsm) { %>import AppError from '../errors/AppError.js';
4
+ <% } else { %>const AppError = require('../errors/AppError');
5
+ <% } %>
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
+ <% } %>function notFound(_req, _res, next) {
10
+ next(new AppError(404, 'Route not found.'));
11
+ }
12
+
13
+ <% if (isEsm) { %>export default notFound;
14
+ <% } else { %>module.exports = notFound;
15
+ <% } %>
@@ -0,0 +1,69 @@
1
+ <% if (educational) { %>// File overview: Encapsulates user data access so controllers/services stay focused on API behavior.
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
+ <% } %><% if (isEsm) { %>import pool from '../db/pool.js';
6
+ <% } else { %>const pool = require('../db/pool');
7
+ <% } %>
8
+
9
+ async function getAll() {
10
+ const result = await pool.query('SELECT id, name, email FROM users ORDER BY id ASC');
11
+ return result.rows;
12
+ }
13
+
14
+ async function create({ name, email }) {
15
+ <% if (educational) { %> // Use parameter placeholders ($1, $2) so pg binds values safely.
16
+ <% } %> const result = await pool.query(
17
+ 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email',
18
+ [name, email]
19
+ );
20
+
21
+ return result.rows[0];
22
+ }
23
+
24
+ <% if (isEsm) { %>export default {
25
+ <% } else { %>module.exports = {
26
+ <% } %> getAll,
27
+ create
28
+ };
29
+ <% } else { %><% if (educational) { %>// Keep data access in one place so the rest of the app does not depend on
30
+ // storage details. This in-memory version can be replaced later with a real
31
+ // database by changing this file only.
32
+ <% } %>const users = [
33
+ {
34
+ id: 1,
35
+ name: 'Ada Lovelace',
36
+ email: 'ada@example.com'
37
+ },
38
+ {
39
+ id: 2,
40
+ name: 'Grace Hopper',
41
+ email: 'grace@example.com'
42
+ }
43
+ ];
44
+
45
+ let nextId = users.length + 1;
46
+
47
+ async function getAll() {
48
+ return users;
49
+ }
50
+
51
+ async function create({ name, email }) {
52
+ const user = {
53
+ id: nextId,
54
+ name,
55
+ email
56
+ };
57
+
58
+ nextId += 1;
59
+ users.push(user);
60
+
61
+ return user;
62
+ }
63
+
64
+ <% if (isEsm) { %>export default {
65
+ <% } else { %>module.exports = {
66
+ <% } %> getAll,
67
+ create
68
+ };
69
+ <% } %>
@@ -0,0 +1,19 @@
1
+ <% if (educational) { %>// File overview: Registers the health-check route used to confirm the API process is running.
2
+ <% } %>
3
+ <% if (isEsm) { %>import { Router } from 'express';
4
+ <% } else { %>const express = require('express');
5
+ <% } %>
6
+
7
+ <% if (isEsm) { %>const router = Router();
8
+ <% } else { %>const router = express.Router();
9
+ <% } %>
10
+
11
+ <% if (educational) { %>// Health-check endpoint used by monitors, load balancers, and container
12
+ // platforms to verify that the API process is running.
13
+ <% } %>router.get('/', (_req, res) => {
14
+ res.status(200).json({ ok: true });
15
+ });
16
+
17
+ <% if (isEsm) { %>export default router;
18
+ <% } else { %>module.exports = router;
19
+ <% } %>
@@ -0,0 +1,22 @@
1
+ <% if (educational) { %>// File overview: Maps /api/users endpoints to controller methods in the MVC request flow.
2
+ <% } %>
3
+ <% if (isEsm) { %>import { Router } from 'express';
4
+
5
+ import usersController from '../controllers/usersController.js';
6
+ <% } else { %>const express = require('express');
7
+
8
+ const usersController = require('../controllers/usersController');
9
+ <% } %>
10
+
11
+ <% if (isEsm) { %>const router = Router();
12
+ <% } else { %>const router = express.Router();
13
+ <% } %>
14
+
15
+ <% if (educational) { %>// Route files should map URLs to controller functions and nothing more.
16
+ // Keeping them thin makes the API surface quick to scan and reason about.
17
+ <% } %>router.get('/', usersController.listUsers);
18
+ router.post('/', usersController.createUser);
19
+
20
+ <% if (isEsm) { %>export default router;
21
+ <% } else { %>module.exports = router;
22
+ <% } %>
@@ -0,0 +1,21 @@
1
+ <% if (educational) { %>// File overview: Application entry point that loads environment variables and starts the HTTP server.
2
+ <% } %>
3
+ <% if (isEsm) { %><% if (educational) { %>// Load variables from .env before other modules read process.env values.
4
+ <% } %>import 'dotenv/config';
5
+
6
+ import app from './app.js';
7
+ import { getPort } from './utils/getPort.js';
8
+ <% } else { %><% if (educational) { %>// Load variables from .env before other modules read process.env values.
9
+ <% } %>require('dotenv').config();
10
+
11
+ const app = require('./app');
12
+ const { getPort } = require('./utils/getPort');
13
+ <% } %>
14
+
15
+ <% if (educational) { %>// Read the server port from PORT, defaulting to 3000 for local development.
16
+ <% } %>const port = getPort(process.env.PORT, 3000);
17
+
18
+ <% if (educational) { %>// Start the HTTP server and listen for incoming requests.
19
+ <% } %>app.listen(port, () => {
20
+ console.log(`Server listening on port ${port}`);
21
+ });
@@ -0,0 +1,34 @@
1
+ <% if (educational) { %>// File overview: Contains user business rules and validation before calling the repository layer.
2
+ <% } %>
3
+ <% if (isEsm) { %>import AppError from '../errors/AppError.js';
4
+ import usersRepository from '../repositories/usersRepository.js';
5
+ <% } else { %>const usersRepository = require('../repositories/usersRepository');
6
+ const AppError = require('../errors/AppError');
7
+ <% } %>
8
+
9
+ <% if (educational) { %>// Services contain business rules and input validation. They sit between
10
+ // controllers (HTTP concerns) and repositories (data access concerns).
11
+ <% } %>async function listUsers() {
12
+ return usersRepository.getAll();
13
+ }
14
+
15
+ async function createUser(payload) {
16
+ const { name, email } = payload;
17
+
18
+ <% if (educational) { %> // Validate input before calling the repository. Throwing AppError provides
19
+ // both the message and status code needed for a clear client response.
20
+ <% } %> if (!name || typeof name !== 'string') {
21
+ throw new AppError(400, '"name" is required.');
22
+ }
23
+
24
+ return usersRepository.create({
25
+ name: name.trim(),
26
+ email: typeof email === 'string' ? email.trim() : null
27
+ });
28
+ }
29
+
30
+ <% if (isEsm) { %>export default {
31
+ <% } else { %>module.exports = {
32
+ <% } %> listUsers,
33
+ createUser
34
+ };
@@ -0,0 +1,18 @@
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
+ <% } %>function getPort(value, fallback = 3000) {
5
+ const parsed = Number(value);
6
+
7
+ if (Number.isInteger(parsed) && parsed > 0) {
8
+ return parsed;
9
+ }
10
+
11
+ return fallback;
12
+ }
13
+
14
+ <% if (isEsm) { %>export { getPort };
15
+ <% } else { %>module.exports = {
16
+ getPort
17
+ };
18
+ <% } %>
@@ -0,0 +1,7 @@
1
+ PORT=3000
2
+ NODE_ENV=development
3
+ <% if (isPostgres) { %>
4
+ # Update with your Postgres credentials if needed
5
+ # Format: postgres://USER:PASSWORD@HOST:PORT/DATABASE
6
+ DATABASE_URL=<%= databaseUrl %>
7
+ <% } %>
@@ -0,0 +1,24 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: {
4
+ node: true,
5
+ es2022: true,
6
+ jest: true
7
+ },
8
+ extends: ['eslint:recommended'],
9
+ parserOptions: {
10
+ ecmaVersion: 'latest',
11
+ <% if (isEsm) { %> sourceType: 'module'
12
+ <% } else { %> sourceType: 'script'
13
+ <% } %> },
14
+ ignorePatterns: ['dist/', 'node_modules/', 'coverage/'],
15
+ rules: {
16
+ 'no-unused-vars': [
17
+ 'error',
18
+ {
19
+ argsIgnorePattern: '^_',
20
+ varsIgnorePattern: '^_'
21
+ }
22
+ ]
23
+ }
24
+ };
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ .env
3
+ dist
4
+ coverage
5
+ npm-debug.log*
6
+ .DS_Store