@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 @@
1
+ {"version":3,"sources":["../src/cli/index.ts","../src/cli/args.ts","../src/cli/output.ts","../src/core/labels.ts","../src/utils/terminalUi.ts","../src/cli/prompts.ts","../src/core/defaults.ts","../src/core/validation.ts","../src/generator/index.ts","../src/utils/paths.ts","../src/utils/exec.ts","../src/utils/files.ts","../src/utils/logger.ts"],"sourcesContent":["import pc from 'picocolors';\nimport { pathToFileURL } from 'node:url';\nimport fs from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\nimport { parseArgs } from './args.js';\nimport { printDryRunPlan, printNextSteps } from './output.js';\nimport { collectSelections, PromptCancelledError } from './prompts.js';\nimport { validateProjectName } from '../core/validation.js';\nimport { generateProject, planProject } from '../generator/index.js';\nimport { commandExists, initGitRepo, installDependencies } from '../utils/exec.js';\nimport { assertSafeTargetDir } from '../utils/files.js';\nimport { logger } from '../utils/logger.js';\nimport { resolveTargetDir } from '../utils/paths.js';\n\nasync function ensurePsqlAvailable(): Promise<void> {\n const hasPsql = await commandExists('psql', ['--version']);\n\n if (!hasPsql) {\n throw new Error(\n [\n 'Postgres (psql) mode requires the `psql` client tool, but it was not found.',\n 'Install Postgres client tools and make sure `psql --version` works, or rerun and choose Postgres (Docker).'\n ].join(' ')\n );\n }\n}\n\nfunction registerSigintHandler(): void {\n process.on('SIGINT', () => {\n logger.warn('Cancelled by user.');\n process.exit(1);\n });\n}\n\nasync function runCli(argv: string[]): Promise<void> {\n const parsedArgs = parseArgs(argv);\n\n for (const unknownFlag of parsedArgs.unknownFlags) {\n logger.warn(`Unknown flag \"${unknownFlag}\" was ignored.`);\n }\n\n const selections = await collectSelections(parsedArgs);\n\n const projectNameError = validateProjectName(selections.projectName);\n\n if (projectNameError) {\n throw new Error(projectNameError);\n }\n\n const targetDir = resolveTargetDir(process.cwd(), selections.projectName);\n\n await assertSafeTargetDir(targetDir);\n\n if (selections.databaseMode === 'postgres-psql') {\n await ensurePsqlAvailable();\n }\n\n const templateConfig = {\n projectName: selections.projectName,\n language: selections.language,\n moduleSystem: selections.moduleSystem,\n jsDevWatcher: selections.jsDevWatcher,\n architecture: selections.architecture,\n educational: selections.educational,\n databaseMode: selections.databaseMode\n };\n\n const plan = await planProject(templateConfig, targetDir);\n\n if (selections.dryRun) {\n printDryRunPlan(selections, plan);\n return;\n }\n\n await generateProject({\n config: templateConfig,\n targetDir\n });\n\n logger.success(`Project files generated at ${targetDir}.`);\n\n if (selections.installDeps) {\n const installCommand = parsedArgs.flags.verbose\n ? 'npm install --no-audit --no-fund'\n : 'npm install --no-audit --no-fund --loglevel=error';\n logger.info(`Installing dependencies (${installCommand})...`);\n await installDependencies(targetDir, parsedArgs.flags.verbose);\n logger.success('Dependencies installed.');\n } else {\n logger.info('Skipped dependency installation.');\n }\n\n if (selections.initGit) {\n logger.info('Initializing git repository...');\n await initGitRepo(targetDir);\n logger.success('Git repository initialized.');\n } else {\n logger.info('Skipped git initialization.');\n }\n\n logger.success('Scaffolding complete.');\n printNextSteps(selections);\n}\n\nfunction isCliEntrypoint(): boolean {\n if (typeof process.argv[1] !== 'string') {\n return false;\n }\n\n try {\n const argvPath = fs.realpathSync(process.argv[1]);\n const modulePath = fs.realpathSync(fileURLToPath(import.meta.url));\n return argvPath === modulePath;\n } catch {\n return pathToFileURL(process.argv[1]).href === import.meta.url;\n }\n}\n\nconst isEntrypoint = isCliEntrypoint();\n\nif (isEntrypoint) {\n registerSigintHandler();\n\n runCli(process.argv.slice(2)).catch((error: unknown) => {\n if (error instanceof PromptCancelledError) {\n logger.warn('Cancelled by user.');\n process.exit(1);\n }\n\n const message = error instanceof Error ? error.message : 'Unexpected error';\n logger.error(message);\n if (error instanceof Error && error.stack) {\n console.error(pc.gray(error.stack));\n }\n process.exit(1);\n });\n}\n\nexport { runCli };\n","import type { ParsedArgs } from '../core/types.js';\n\nconst TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);\nconst FALSE_VALUES = new Set(['0', 'false', 'no', 'off']);\n\nfunction parseBooleanValue(value: string | undefined): boolean | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n const normalized = value.trim().toLowerCase();\n\n if (TRUE_VALUES.has(normalized)) {\n return true;\n }\n\n if (FALSE_VALUES.has(normalized)) {\n return false;\n }\n\n return undefined;\n}\n\nfunction splitFlag(token: string): { name: string; value: string | undefined } {\n const withoutPrefix = token.slice(2);\n const equalsIndex = withoutPrefix.indexOf('=');\n\n if (equalsIndex === -1) {\n return {\n name: withoutPrefix,\n value: undefined\n };\n }\n\n return {\n name: withoutPrefix.slice(0, equalsIndex),\n value: withoutPrefix.slice(equalsIndex + 1)\n };\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const flags = {\n yes: false,\n dryRun: false,\n install: true,\n git: true,\n verbose: false\n };\n\n const provided = {\n yes: false,\n dryRun: false,\n install: false,\n git: false,\n verbose: false\n };\n\n const unknownFlags: string[] = [];\n const positionals: string[] = [];\n\n let positionalOnly = false;\n\n for (const token of argv) {\n if (positionalOnly) {\n positionals.push(token);\n continue;\n }\n\n if (token === '--') {\n positionalOnly = true;\n continue;\n }\n\n if (!token.startsWith('-') || token === '-') {\n positionals.push(token);\n continue;\n }\n\n if (!token.startsWith('--')) {\n unknownFlags.push(token);\n continue;\n }\n\n const { name, value } = splitFlag(token);\n\n if (name === 'yes') {\n const parsedValue = parseBooleanValue(value);\n if (value !== undefined && parsedValue === undefined) {\n unknownFlags.push(token);\n continue;\n }\n flags.yes = parsedValue ?? true;\n provided.yes = true;\n continue;\n }\n\n if (name === 'dry-run') {\n const parsedValue = parseBooleanValue(value);\n if (value !== undefined && parsedValue === undefined) {\n unknownFlags.push(token);\n continue;\n }\n flags.dryRun = parsedValue ?? true;\n provided.dryRun = true;\n continue;\n }\n\n if (name === 'no-install') {\n const parsedValue = parseBooleanValue(value);\n if (value !== undefined && parsedValue === undefined) {\n unknownFlags.push(token);\n continue;\n }\n\n const noInstall = parsedValue ?? true;\n flags.install = !noInstall;\n provided.install = true;\n continue;\n }\n\n if (name === 'no-git') {\n const parsedValue = parseBooleanValue(value);\n if (value !== undefined && parsedValue === undefined) {\n unknownFlags.push(token);\n continue;\n }\n\n const noGit = parsedValue ?? true;\n flags.git = !noGit;\n provided.git = true;\n continue;\n }\n\n if (name === 'verbose') {\n const parsedValue = parseBooleanValue(value);\n if (value !== undefined && parsedValue === undefined) {\n unknownFlags.push(token);\n continue;\n }\n\n flags.verbose = parsedValue ?? true;\n provided.verbose = true;\n continue;\n }\n\n unknownFlags.push(token);\n }\n\n return {\n projectName: positionals[0],\n positionals,\n unknownFlags,\n flags,\n provided\n };\n}\n","import path from 'node:path';\nimport pc from 'picocolors';\n\nimport {\n architectureLabel,\n databaseLabel,\n jsDevWatcherLabel,\n languageLabel,\n moduleSystemLabel,\n} from '../core/labels.js';\nimport type { GenerationPlan, UserSelections } from '../core/types.js';\nimport {\n formatCommandLines,\n formatKeyValueLines,\n printCard,\n} from '../utils/terminalUi.js';\n\nfunction buildNextStepCommands(selection: UserSelections): string[] {\n const commands = [`cd ${selection.projectName}`];\n\n if (!selection.installDeps) {\n commands.push('npm install');\n }\n\n commands.push('cp .env.example .env');\n\n if (selection.databaseMode === 'postgres-psql') {\n commands.push('npm run db:create');\n commands.push('npm run db:setup');\n commands.push('npm run db:seed');\n }\n\n if (selection.databaseMode === 'postgres-docker') {\n commands.push('npm run db:up');\n commands.push('npm run db:setup');\n commands.push('npm run db:seed');\n }\n\n commands.push('npm run dev');\n\n if (selection.language === 'ts') {\n commands.push('npm run build');\n }\n\n commands.push('npm test');\n\n return commands;\n}\n\nexport function printDryRunPlan(\n selection: UserSelections,\n plan: GenerationPlan,\n): void {\n const languageValue =\n selection.language === 'js'\n ? `${languageLabel(selection.language)} (${moduleSystemLabel(selection.moduleSystem)})`\n : languageLabel(selection.language);\n\n const summaryEntries: Parameters<typeof formatKeyValueLines>[0] = [\n {\n key: 'Target',\n value: formatTargetPath(plan.targetDir),\n tone: 'accent',\n },\n {\n key: 'Language',\n value: languageValue,\n tone: 'accent',\n },\n {\n key: 'Architecture',\n value: architectureLabel(selection.architecture),\n tone: 'accent',\n },\n {\n key: 'Database',\n value: databaseLabel(selection.databaseMode),\n tone: 'accent',\n },\n ];\n\n if (selection.language === 'js') {\n summaryEntries.push({\n key: 'Dev watcher',\n value: jsDevWatcherLabel(selection.jsDevWatcher),\n tone: 'accent',\n });\n }\n\n summaryEntries.push(\n {\n key: 'Educational',\n value: selection.educational ? 'On' : 'Off',\n tone: selection.educational ? 'success' : 'muted',\n },\n {\n key: 'Install deps',\n value: selection.installDeps ? 'Yes' : 'No',\n tone: selection.installDeps ? 'success' : 'warn',\n },\n {\n key: 'Init git',\n value: selection.initGit ? 'Yes' : 'No',\n tone: selection.initGit ? 'success' : 'warn',\n },\n );\n\n const summaryLines = formatKeyValueLines(summaryEntries);\n\n const fileLines = plan.files.map((file) => `${pc.dim('-')} ${file.outputRelativePath}`);\n\n console.log('');\n printCard('Dry Run: Configuration', summaryLines);\n console.log('');\n printCard(`Dry Run: Files (${plan.files.length})`, fileLines);\n}\n\nexport function printNextSteps(selection: UserSelections): void {\n const stackParts = [\n selection.language === 'js'\n ? `${languageLabel(selection.language)} (${moduleSystemLabel(selection.moduleSystem)})`\n : languageLabel(selection.language),\n architectureLabel(selection.architecture),\n databaseLabel(selection.databaseMode),\n ];\n\n const summaryEntries: Parameters<typeof formatKeyValueLines>[0] = [\n {\n key: 'Project',\n value: selection.projectName,\n tone: 'accent',\n },\n {\n key: 'Stack',\n value: stackParts.join(' | '),\n tone: 'accent',\n },\n {\n key: 'Educational',\n value: selection.educational ? 'On' : 'Off',\n tone: selection.educational ? 'success' : 'muted',\n },\n ];\n\n if (selection.language === 'js') {\n summaryEntries.push({\n key: 'Dev watcher',\n value: jsDevWatcherLabel(selection.jsDevWatcher),\n tone: 'accent',\n });\n }\n\n const summaryLines = formatKeyValueLines(summaryEntries);\n\n const nextStepCommands = buildNextStepCommands(selection);\n\n console.log('');\n printCard('Project Ready', summaryLines);\n console.log('');\n printCard('Next Steps', formatCommandLines(nextStepCommands));\n\n if (selection.databaseMode === 'postgres-psql') {\n const setupLines = [\n pc.yellow('Linux first-time setup (run once if needed):'),\n pc.dim('# Create a Postgres role matching your OS user'),\n ...formatCommandLines([\n 'sudo -u postgres createuser --createdb \"$USER\"',\n `sudo -u postgres psql -c \"ALTER USER \\\\\"$USER\\\\\" WITH PASSWORD 'postgres';\"`,\n ]),\n ];\n\n console.log('');\n printCard('Postgres Setup', setupLines);\n }\n}\n\nexport function formatTargetPath(targetDir: string): string {\n const relative = path.relative(process.cwd(), targetDir);\n return relative || '.';\n}\n","import type {\n Architecture,\n DatabaseMode,\n JsDevWatcher,\n Language,\n ModuleSystem,\n} from './types.js';\n\nexport function languageLabel(language: Language): string {\n return language === 'ts' ? 'TypeScript' : 'JavaScript';\n}\n\nexport function moduleSystemLabel(moduleSystem: ModuleSystem): string {\n return moduleSystem === 'esm' ? 'ES Modules' : 'CommonJS';\n}\n\nexport function jsDevWatcherLabel(jsDevWatcher: JsDevWatcher): string {\n return jsDevWatcher === 'nodemon' ? 'nodemon' : 'node --watch';\n}\n\nexport function architectureLabel(architecture: Architecture): string {\n return architecture === 'mvc' ? 'MVC' : 'Simple';\n}\n\nexport function databaseLabel(databaseMode: DatabaseMode): string {\n if (databaseMode === 'postgres-psql') {\n return 'Postgres (psql)';\n }\n\n if (databaseMode === 'postgres-docker') {\n return 'Postgres (Docker)';\n }\n\n return 'In-memory';\n}\n","import pc from 'picocolors';\n\ntype StatusTone = 'info' | 'success' | 'warn' | 'error';\ntype ValueTone = 'default' | 'accent' | 'success' | 'warn' | 'muted';\n\nconst ANSI_PATTERN = /\\u001b\\[[0-9;]*m/g;\n\nfunction stripAnsi(value: string): string {\n return value.replace(ANSI_PATTERN, '');\n}\n\nfunction displayLength(value: string): number {\n return stripAnsi(value).length;\n}\n\nfunction padDisplay(value: string, width: number): string {\n const padding = width - displayLength(value);\n if (padding <= 0) {\n return value;\n }\n\n return `${value}${' '.repeat(padding)}`;\n}\n\nfunction minimumContentWidth(lines: string[]): number {\n return lines.reduce((max, line) => {\n return Math.max(max, displayLength(line));\n }, 0);\n}\n\nfunction styleValue(value: string, tone: ValueTone): string {\n if (tone === 'accent') {\n return pc.cyan(value);\n }\n\n if (tone === 'success') {\n return pc.green(value);\n }\n\n if (tone === 'warn') {\n return pc.yellow(value);\n }\n\n if (tone === 'muted') {\n return pc.dim(value);\n }\n\n return value;\n}\n\nexport function statusTag(tone: StatusTone): string {\n if (tone === 'success') {\n return pc.bold(pc.green('[ok]'));\n }\n\n if (tone === 'warn') {\n return pc.bold(pc.yellow('[!!]'));\n }\n\n if (tone === 'error') {\n return pc.bold(pc.red('[x]'));\n }\n\n return pc.bold(pc.cyan('[..]'));\n}\n\ninterface KeyValueRow {\n key: string;\n value: string;\n tone?: ValueTone;\n}\n\nexport function formatKeyValueLines(rows: KeyValueRow[]): string[] {\n const keyWidth = rows.reduce((max, row) => Math.max(max, row.key.length), 0);\n\n return rows.map((row) => {\n const key = pc.bold(padDisplay(row.key, keyWidth));\n const value = styleValue(row.value, row.tone ?? 'default');\n return `${key} ${value}`;\n });\n}\n\nexport function formatCommandLines(commands: string[]): string[] {\n return commands.map((command) => pc.bold(pc.cyan(command)));\n}\n\nexport function printCard(title: string, lines: string[]): void {\n const content = lines.length > 0 ? lines : [pc.dim('(none)')];\n const width = Math.max(30, minimumContentWidth([title, ...content]));\n\n const border = pc.dim(pc.cyan(`+${'-'.repeat(width + 2)}+`));\n const edge = pc.dim(pc.cyan('|'));\n const divider = pc.dim('-'.repeat(width));\n\n console.log(border);\n console.log(\n `${edge} ${padDisplay(pc.bold(pc.cyan(title)), width)} ${edge}`,\n );\n console.log(`${edge} ${divider} ${edge}`);\n\n for (const line of content) {\n console.log(`${edge} ${padDisplay(line, width)} ${edge}`);\n }\n\n console.log(border);\n}\n","import {\n confirm,\n intro,\n isCancel,\n outro,\n select,\n text\n} from '@clack/prompts';\nimport pc from 'picocolors';\n\nimport { DEFAULT_PROJECT_NAME, DEFAULT_SELECTIONS } from '../core/defaults.js';\nimport type { ParsedArgs, UserSelections } from '../core/types.js';\n\nexport class PromptCancelledError extends Error {\n constructor() {\n super('Prompt cancelled by user.');\n }\n}\n\nfunction unwrapPrompt<T>(value: T | symbol): T {\n if (isCancel(value)) {\n throw new PromptCancelledError();\n }\n\n return value as T;\n}\n\nexport async function collectSelections(parsedArgs: ParsedArgs): Promise<UserSelections> {\n if (parsedArgs.flags.yes || !process.stdin.isTTY) {\n return {\n projectName: parsedArgs.projectName ?? DEFAULT_PROJECT_NAME,\n language: DEFAULT_SELECTIONS.language,\n moduleSystem: DEFAULT_SELECTIONS.moduleSystem,\n jsDevWatcher: DEFAULT_SELECTIONS.jsDevWatcher,\n architecture: DEFAULT_SELECTIONS.architecture,\n databaseMode: DEFAULT_SELECTIONS.databaseMode,\n educational: DEFAULT_SELECTIONS.educational,\n installDeps: parsedArgs.flags.install,\n initGit: parsedArgs.flags.git,\n dryRun: parsedArgs.flags.dryRun\n };\n }\n\n intro(\n [\n pc.bold(pc.cyan('Create Express API Starter')),\n pc.dim('Scaffold an Express backend with practical defaults.')\n ].join('\\n')\n );\n\n const projectName = parsedArgs.projectName\n ? parsedArgs.projectName\n : unwrapPrompt(\n await text({\n message: 'Project name',\n placeholder: DEFAULT_PROJECT_NAME,\n defaultValue: DEFAULT_PROJECT_NAME,\n validate(value) {\n if (!value.trim()) {\n return 'Project name is required.';\n }\n\n return undefined;\n }\n })\n );\n\n const language = unwrapPrompt(\n await select({\n message: 'Language',\n initialValue: DEFAULT_SELECTIONS.language,\n options: [\n {\n value: 'js',\n label: 'JavaScript'\n },\n {\n value: 'ts',\n label: 'TypeScript'\n }\n ]\n })\n ) as UserSelections['language'];\n\n const moduleSystem =\n language === 'js'\n ? (unwrapPrompt(\n await select({\n message: 'Module system',\n initialValue: DEFAULT_SELECTIONS.moduleSystem,\n options: [\n {\n value: 'commonjs',\n label: 'CommonJS'\n },\n {\n value: 'esm',\n label: 'ES Modules'\n }\n ]\n })\n ) as UserSelections['moduleSystem'])\n : 'commonjs';\n\n const jsDevWatcher =\n language === 'js'\n ? (unwrapPrompt(\n await select({\n message: 'Dev watcher (JavaScript)',\n initialValue: DEFAULT_SELECTIONS.jsDevWatcher,\n options: [\n {\n value: 'node-watch',\n label: 'node --watch (built-in)'\n },\n {\n value: 'nodemon',\n label: 'nodemon'\n }\n ]\n })\n ) as UserSelections['jsDevWatcher'])\n : DEFAULT_SELECTIONS.jsDevWatcher;\n\n const architecture = unwrapPrompt(\n await select({\n message: 'Architecture',\n initialValue: DEFAULT_SELECTIONS.architecture,\n options: [\n {\n value: 'simple',\n label: 'Simple'\n },\n {\n value: 'mvc',\n label: 'MVC'\n }\n ]\n })\n ) as UserSelections['architecture'];\n\n const databaseMode = unwrapPrompt(\n await select({\n message: 'Database',\n initialValue: DEFAULT_SELECTIONS.databaseMode,\n options: [\n {\n value: 'memory',\n label: 'In-memory'\n },\n {\n value: 'postgres-psql',\n label: 'Postgres (psql)'\n },\n {\n value: 'postgres-docker',\n label: 'Postgres (Docker)'\n }\n ]\n })\n ) as UserSelections['databaseMode'];\n\n const educational = unwrapPrompt(\n await confirm({\n message: 'Add educational comments',\n initialValue: DEFAULT_SELECTIONS.educational\n })\n );\n\n const installDeps = parsedArgs.provided.install\n ? parsedArgs.flags.install\n : unwrapPrompt(\n await confirm({\n message: 'Install dependencies now',\n initialValue: DEFAULT_SELECTIONS.installDeps\n })\n );\n\n const initGit = parsedArgs.provided.git\n ? parsedArgs.flags.git\n : unwrapPrompt(\n await confirm({\n message: 'Initialize git repository',\n initialValue: DEFAULT_SELECTIONS.initGit\n })\n );\n\n outro(pc.cyan('Scaffolding project files...'));\n\n return {\n projectName,\n language,\n moduleSystem,\n jsDevWatcher,\n architecture,\n databaseMode,\n educational,\n installDeps,\n initGit,\n dryRun: parsedArgs.flags.dryRun\n };\n}\n","import type { UserSelections } from './types.js';\n\nexport const DEFAULT_PROJECT_NAME = 'my-api';\n\nexport const DEFAULT_SELECTIONS: Omit<UserSelections, 'projectName' | 'dryRun'> = {\n language: 'js',\n moduleSystem: 'commonjs',\n jsDevWatcher: 'node-watch',\n architecture: 'simple',\n databaseMode: 'memory',\n educational: true,\n installDeps: true,\n initGit: true\n};\n","import path from 'node:path';\n\nexport function validateProjectName(projectName: string): string | null {\n const trimmed = projectName.trim();\n\n if (!trimmed) {\n return 'Project name is required.';\n }\n\n if (trimmed === '.' || trimmed === '..') {\n return 'Project name cannot be \".\" or \"..\".';\n }\n\n if (trimmed !== path.basename(trimmed)) {\n return 'Project name must be a folder name, not a path.';\n }\n\n if (/[^a-zA-Z0-9._-]/.test(trimmed)) {\n return 'Project name can only include letters, numbers, \".\", \"_\", and \"-\".';\n }\n\n return null;\n}\n","import path from 'node:path';\nimport os from 'node:os';\nimport fs from 'fs-extra';\nimport ejs from 'ejs';\n\nimport {\n architectureLabel,\n databaseLabel,\n jsDevWatcherLabel,\n languageLabel,\n moduleSystemLabel,\n} from '../core/labels.js';\nimport type {\n GenerationPlan,\n PlannedFile,\n TemplateConfig,\n} from '../core/types.js';\nimport { resolveTemplatesDir } from '../utils/paths.js';\n\ninterface GenerateProjectInput {\n config: TemplateConfig;\n targetDir: string;\n dryRun?: boolean;\n}\n\nfunction toPosixPath(value: string): string {\n return value.split(path.sep).join('/');\n}\n\nfunction isEjsTemplate(relativePath: string): boolean {\n return relativePath.endsWith('.ejs');\n}\n\nfunction stripEjsSuffix(relativePath: string): string {\n return relativePath.endsWith('.ejs')\n ? relativePath.slice(0, -'.ejs'.length)\n : relativePath;\n}\n\nfunction resolveTemplateRoot(config: TemplateConfig): string {\n const templatesDir = resolveTemplatesDir();\n return path.join(templatesDir, config.language, config.architecture);\n}\n\nasync function listFilesRecursive(\n directory: string,\n baseDir: string = directory,\n): Promise<string[]> {\n const entries = await fs.readdir(directory, {\n withFileTypes: true,\n });\n\n const sortedEntries = entries.sort((a, b) => a.name.localeCompare(b.name));\n const results: string[] = [];\n\n for (const entry of sortedEntries) {\n const entryPath = path.join(directory, entry.name);\n\n if (entry.isDirectory()) {\n const childEntries = await listFilesRecursive(entryPath, baseDir);\n results.push(...childEntries);\n continue;\n }\n\n results.push(path.relative(baseDir, entryPath));\n }\n\n return results;\n}\n\nfunction shouldIncludeTemplate(\n relativePath: string,\n config: TemplateConfig,\n): boolean {\n if (relativePath === 'compose.yaml.ejs') {\n return config.databaseMode === 'postgres-docker';\n }\n\n if (relativePath === 'scripts/dbCreate.js.ejs') {\n return config.databaseMode === 'postgres-psql';\n }\n\n if (relativePath.startsWith('scripts/')) {\n return config.databaseMode !== 'memory';\n }\n\n if (relativePath.startsWith('db/')) {\n return config.databaseMode !== 'memory';\n }\n\n if (relativePath.startsWith('src/db/')) {\n return config.databaseMode !== 'memory';\n }\n\n return true;\n}\n\nfunction toPlannedFile(relativeTemplatePath: string): PlannedFile {\n return {\n templateRelativePath: relativeTemplatePath,\n outputRelativePath: stripEjsSuffix(relativeTemplatePath),\n isTemplate: isEjsTemplate(relativeTemplatePath),\n };\n}\n\nfunction toPackageName(projectName: string): string {\n const cleaned = projectName\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/^-+/, '')\n .replace(/-+$/, '');\n\n return cleaned || 'express-api';\n}\n\nfunction toDatabaseName(projectName: string): string {\n const cleaned = projectName\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+/, '')\n .replace(/_+$/, '');\n\n return (cleaned || 'express_api') + '_dev';\n}\n\nfunction getOsUsername(): string {\n try {\n return os.userInfo().username;\n } catch {\n return process.env.USER ?? process.env.USERNAME ?? 'postgres';\n }\n}\n\nfunction templateData(config: TemplateConfig): Record<string, unknown> {\n const isTypeScript = config.language === 'ts';\n const isEsm = config.moduleSystem === 'esm';\n const isJavaScript = config.language === 'js';\n const useNodemon = isJavaScript && config.jsDevWatcher === 'nodemon';\n const isPostgres = config.databaseMode !== 'memory';\n const isDocker = config.databaseMode === 'postgres-docker';\n const isPsql = config.databaseMode === 'postgres-psql';\n const dbName = toDatabaseName(config.projectName);\n const username = isPostgres ? getOsUsername() : '';\n\n return {\n ...config,\n isTypeScript,\n isEsm,\n isCommonJs: !isEsm,\n isPostgres,\n isDocker,\n isPsql,\n packageName: toPackageName(config.projectName),\n databaseName: dbName,\n educationalLabel: config.educational ? 'On' : 'Off',\n languageLabel: languageLabel(config.language),\n moduleSystemLabel: moduleSystemLabel(config.moduleSystem),\n architectureLabel: architectureLabel(config.architecture),\n databaseLabel: databaseLabel(config.databaseMode),\n jsDevWatcherLabel: jsDevWatcherLabel(config.jsDevWatcher),\n jsDevCommand: useNodemon\n ? 'nodemon src/server.js'\n : 'node --watch src/server.js',\n useNodemon,\n databaseUrl:\n config.databaseMode === 'postgres-docker'\n ? `postgres://postgres:postgres@localhost:5433/${dbName}`\n : `postgres://${encodeURIComponent(username)}:postgres@localhost:5432/${dbName}`,\n osUsername: username,\n };\n}\n\nfunction fromPosixPath(relativePath: string): string {\n return relativePath.split('/').join(path.sep);\n}\n\nexport async function planProject(\n config: TemplateConfig,\n targetDir: string,\n): Promise<GenerationPlan> {\n const templateRoot = resolveTemplateRoot(config);\n const templateRootExists = await fs.pathExists(templateRoot);\n\n if (!templateRootExists) {\n throw new Error(`Template root not found: ${templateRoot}`);\n }\n\n const allFiles = await listFilesRecursive(templateRoot);\n\n const files = allFiles\n .map(toPosixPath)\n .filter((relativePath) => shouldIncludeTemplate(relativePath, config))\n .map(toPlannedFile);\n\n return {\n targetDir,\n actions: [\n `Create project directory: ${targetDir}`,\n `Write ${files.length} files`,\n ],\n files,\n };\n}\n\nexport async function generateProject({\n config,\n targetDir,\n dryRun = false,\n}: GenerateProjectInput): Promise<GenerationPlan> {\n const templateRoot = resolveTemplateRoot(config);\n const plan = await planProject(config, targetDir);\n\n if (dryRun) {\n return plan;\n }\n\n await fs.ensureDir(targetDir);\n\n const data = templateData(config);\n\n for (const file of plan.files) {\n const sourcePath = path.join(\n templateRoot,\n fromPosixPath(file.templateRelativePath),\n );\n const destinationPath = path.join(\n targetDir,\n fromPosixPath(file.outputRelativePath),\n );\n\n await fs.ensureDir(path.dirname(destinationPath));\n\n if (file.isTemplate) {\n const template = await fs.readFile(sourcePath, 'utf8');\n const rendered = ejs.render(template, data);\n await fs.writeFile(destinationPath, rendered, 'utf8');\n continue;\n }\n\n await fs.copy(sourcePath, destinationPath);\n }\n\n return plan;\n}\n","import path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport fs from 'fs-extra';\n\nexport function resolveTargetDir(baseDir: string, projectName: string): string {\n return path.resolve(baseDir, projectName);\n}\n\nexport function resolveTemplatesDir(): string {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n const candidates = [\n path.resolve(moduleDir, '../templates'),\n path.resolve(moduleDir, '../../templates'),\n path.resolve(process.cwd(), 'templates')\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n return candidate;\n }\n }\n\n throw new Error('Unable to locate templates directory.');\n}\n","import { execa } from 'execa';\n\nexport async function commandExists(command: string, args: string[] = ['--version']): Promise<boolean> {\n try {\n await execa(command, args, {\n stdio: 'ignore'\n });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function runCommand(command: string, args: string[], cwd: string): Promise<void> {\n await execa(command, args, {\n cwd,\n stdio: 'inherit'\n });\n}\n\nexport async function installDependencies(cwd: string, verbose = false): Promise<void> {\n const args = ['install', '--no-audit', '--no-fund'];\n\n if (!verbose) {\n args.push('--loglevel=error');\n }\n\n await runCommand('npm', args, cwd);\n}\n\nexport async function initGitRepo(cwd: string): Promise<void> {\n await runCommand('git', ['init'], cwd);\n}\n","import path from 'node:path';\nimport fs from 'fs-extra';\n\nexport async function assertSafeTargetDir(targetDir: string): Promise<void> {\n const exists = await fs.pathExists(targetDir);\n\n if (!exists) {\n return;\n }\n\n const stats = await fs.stat(targetDir);\n\n if (!stats.isDirectory()) {\n throw new Error(`Target path already exists and is not a directory: ${targetDir}`);\n }\n\n const entries = await fs.readdir(targetDir);\n if (entries.length > 0) {\n throw new Error(\n `Target directory \"${path.basename(targetDir)}\" already exists and is not empty.`\n );\n }\n}\n","import { statusTag } from './terminalUi.js';\n\nexport const logger = {\n info(message: string): void {\n console.log(`${statusTag('info')} ${message}`);\n },\n success(message: string): void {\n console.log(`${statusTag('success')} ${message}`);\n },\n warn(message: string): void {\n console.warn(`${statusTag('warn')} ${message}`);\n },\n error(message: string): void {\n console.error(`${statusTag('error')} ${message}`);\n }\n};\n"],"mappings":"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","names":["pc","fs","fileURLToPath","pc","pc","pc","pc","path","path","fs","path","path","fs","path","fs","fs","fileURLToPath","pc"]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@alexmc2/create-express-api-starter",
3
+ "version": "0.1.0",
4
+ "description": "Interactive CLI to scaffold API-first Express backend projects",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/alexmc2/create-express-api-starter.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/alexmc2/create-express-api-starter/issues"
13
+ },
14
+ "homepage": "https://github.com/alexmc2/create-express-api-starter#readme",
15
+ "keywords": [
16
+ "express",
17
+ "express-generator",
18
+ "scaffold",
19
+ "cli",
20
+ "starter",
21
+ "boilerplate",
22
+ "api",
23
+ "rest",
24
+ "typescript",
25
+ "javascript",
26
+ "postgres",
27
+ "jest",
28
+ "supertest"
29
+ ],
30
+ "bin": {
31
+ "create-express-api-starter": "dist/cli.js"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "templates",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "scripts": {
43
+ "dev": "tsx src/cli/index.ts",
44
+ "build": "tsup",
45
+ "test": "vitest run",
46
+ "test:unit": "vitest run --exclude test/integration.generated-projects.test.ts",
47
+ "pack": "npm pack",
48
+ "prepack": "npm run build",
49
+ "prepublishOnly": "npm run test:unit"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "dependencies": {
55
+ "@clack/prompts": "^0.10.1",
56
+ "ejs": "^3.1.10",
57
+ "execa": "^9.5.2",
58
+ "fs-extra": "^11.2.0",
59
+ "picocolors": "^1.1.1"
60
+ },
61
+ "devDependencies": {
62
+ "@types/ejs": "^3.1.5",
63
+ "@types/fs-extra": "^11.0.4",
64
+ "@types/node": "^20.17.0",
65
+ "tsup": "^8.3.5",
66
+ "tsx": "^4.19.2",
67
+ "typescript": "^5.7.2",
68
+ "vitest": "^2.1.8"
69
+ }
70
+ }
@@ -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
@@ -0,0 +1,187 @@
1
+ # <%= projectName %>
2
+
3
+ Express API starter generated by `@alexmc2/create-express-api-starter`.
4
+
5
+ ## Selected options
6
+
7
+ - Language: <%= languageLabel %>
8
+ - Module system: <%= moduleSystemLabel %>
9
+ - Dev watcher: <%= jsDevWatcherLabel %>
10
+ - Architecture: <%= architectureLabel %>
11
+ - Database: <%= databaseLabel %>
12
+ - Educational comments: <%= educationalLabel %>
13
+
14
+ ## Folder structure
15
+
16
+ ```
17
+ .
18
+ ├── src
19
+ │ ├── app.js
20
+ │ ├── server.js
21
+ │ ├── routes
22
+ │ │ ├── health.js
23
+ │ │ └── users.js
24
+ │ ├── controllers
25
+ │ │ └── usersController.js
26
+ │ ├── services
27
+ │ │ └── usersService.js
28
+ │ ├── repositories
29
+ │ │ └── usersRepository.js
30
+ <% if (isPostgres) { %>│ ├── db
31
+ │ │ └── pool.js
32
+ <% } %>│ ├── errors
33
+ │ │ └── AppError.js
34
+ │ ├── utils
35
+ │ │ └── getPort.js
36
+ │ └── middleware
37
+ │ ├── errorHandler.js
38
+ │ └── notFound.js
39
+ ├── __tests__
40
+ │ └── app.test.js
41
+ <% if (isPostgres) { %>├── db
42
+ │ ├── schema.sql
43
+ │ └── seed.sql
44
+ <% } %><% if (isPostgres) { %>├── scripts
45
+ <% if (isPsql) { %>│ ├── dbCreate.js
46
+ <% } %>│ ├── dbSetup.js
47
+ │ ├── dbSeed.js
48
+ │ └── dbReset.js
49
+ <% } %><% if (isDocker) { %>├── compose.yaml
50
+ <% } %>├── .env.example
51
+ ├── .gitignore
52
+ ├── .eslintrc.cjs
53
+ ├── package.json
54
+ └── jest.config.js
55
+ ```
56
+
57
+ <% if (isPsql) { %>
58
+ ## Prerequisites
59
+
60
+ This project uses a local PostgreSQL database. Follow the steps for your OS below.
61
+
62
+ ### 1. Install PostgreSQL
63
+
64
+ **macOS** (using [Homebrew](https://brew.sh)):
65
+
66
+ ```sh
67
+ brew install postgresql@17
68
+ brew services start postgresql@17
69
+ ```
70
+
71
+ **Ubuntu / Debian**:
72
+
73
+ ```sh
74
+ sudo apt update && sudo apt install postgresql postgresql-contrib
75
+ sudo systemctl start postgresql && sudo systemctl enable postgresql
76
+ ```
77
+
78
+ **Windows**: Download the installer from https://www.postgresql.org/download/windows/ and follow the prompts. Remember the password you set — you'll need it in step 2.
79
+
80
+ ### 2. Set up your database role
81
+
82
+ Your `.env` file connects as `<%= osUsername %>` with password `postgres`. You need a matching PostgreSQL role.
83
+
84
+ **Linux** (run once):
85
+
86
+ ```sh
87
+ sudo -u postgres createuser --createdb "$USER"
88
+ sudo -u postgres psql -c "ALTER USER \"$USER\" WITH PASSWORD 'postgres';"
89
+ ```
90
+
91
+ **macOS**: Homebrew already created a role for you. Run the commands above only if you get an auth error.
92
+
93
+ **Windows**: The installer created a `postgres` role. Edit `DATABASE_URL` in your `.env` to use it:
94
+
95
+ ```
96
+ DATABASE_URL=postgres://postgres:YOUR_INSTALL_PASSWORD@localhost:5432/<%= databaseName %>
97
+ ```
98
+
99
+ ### 3. Verify
100
+
101
+ ```sh
102
+ pg_isready
103
+ ```
104
+
105
+ You should see `accepting connections`.
106
+ <% } %>
107
+ <% if (isDocker) { %>
108
+ ## Prerequisites
109
+
110
+ This project uses PostgreSQL in a Docker container. Install [Docker Desktop](https://docs.docker.com/get-docker/) and make sure it's running before continuing.
111
+ <% } %>
112
+
113
+ ## Run the app
114
+
115
+ ```sh
116
+ npm install
117
+ cp .env.example .env
118
+ <% if (isPsql) { %>npm run db:create
119
+ npm run db:setup
120
+ npm run db:seed
121
+ <% } else if (isDocker) { %>npm run db:up
122
+ npm run db:setup
123
+ npm run db:seed
124
+ <% } %>npm run dev
125
+ ```
126
+
127
+ Run `npm run lint` to check the code with ESLint.
128
+
129
+ ## Add a new route
130
+
131
+ 1. Create a router file in `src/routes`.
132
+ 2. Add a controller function in `src/controllers`.
133
+ 3. Add business logic in `src/services`.
134
+ 4. Register the route in `src/app.js`.
135
+
136
+ ## Where business logic goes
137
+
138
+ Put business rules in `src/services`. Controllers focus on HTTP concerns, and repositories only handle data access.
139
+
140
+ ## How errors work
141
+
142
+ - `src/middleware/notFound.js` handles unknown routes with a 404 JSON response.
143
+ - `src/middleware/errorHandler.js` returns `{ status, message }` and includes `stack` only in development.
144
+
145
+ <% if (isPsql) { %>
146
+ ## Database commands
147
+
148
+ | Command | What it does |
149
+ | --- | --- |
150
+ | `npm run db:create` | Creates the database (safe to re-run) |
151
+ | `npm run db:setup` | Applies the schema (creates tables) |
152
+ | `npm run db:seed` | Inserts sample data |
153
+ | `npm run db:reset` | Drops and re-creates tables + sample data |
154
+
155
+ ## Troubleshooting
156
+
157
+ **"connection refused"** — PostgreSQL isn't running.
158
+
159
+ ```sh
160
+ # Linux
161
+ sudo systemctl start postgresql
162
+ # macOS
163
+ brew services start postgresql@17
164
+ ```
165
+
166
+ **"role does not exist"** — Create a Postgres role for your OS user:
167
+
168
+ ```sh
169
+ sudo -u postgres createuser --createdb "$USER"
170
+ sudo -u postgres psql -c "ALTER USER \"$USER\" WITH PASSWORD 'postgres';"
171
+ ```
172
+
173
+ **"password authentication failed" / "client password must be a string"** — The credentials in `DATABASE_URL` are wrong or missing. Run the role setup commands in [Prerequisites](#prerequisites) and make sure `DATABASE_URL` in `.env` matches.
174
+
175
+ **"database does not exist"** — Run `npm run db:create`.
176
+ <% } %>
177
+ <% if (isDocker) { %>
178
+ ## Database commands
179
+
180
+ | Command | What it does |
181
+ | --- | --- |
182
+ | `npm run db:up` | Starts the Postgres Docker container |
183
+ | `npm run db:down` | Stops the container |
184
+ | `npm run db:setup` | Applies the schema (creates tables) |
185
+ | `npm run db:seed` | Inserts sample data |
186
+ | `npm run db:reset` | Stops container, restarts, re-applies schema + seed |
187
+ <% } %>
@@ -0,0 +1,51 @@
1
+ <% if (educational) { %>// File overview: Basic endpoint tests that verify the API responds with expected status codes and payloads.
2
+ <% } %>
3
+ <% if (educational) { %>// Supertest sends HTTP requests directly to the Express app instance.
4
+ // This keeps tests fast and isolated because no network port is opened.
5
+ <% } %><% if (isEsm) { %>import { describe, expect, test<% if (isPostgres) { %>, afterAll<% } %> } from '@jest/globals';
6
+ import request from 'supertest';
7
+
8
+ import app from '../src/app.js';
9
+ <% if (isPostgres) { %>import pool from '../src/db/pool.js';
10
+ <% } %><% } else { %>const request = require('supertest');
11
+
12
+ const app = require('../src/app');
13
+ <% if (isPostgres) { %>const pool = require('../src/db/pool');
14
+ <% } %><% } %>
15
+ <% if (isPostgres) { %>
16
+ afterAll(async () => {
17
+ await pool.end();
18
+ });
19
+
20
+ <% } %>describe('API', () => {
21
+ test('GET / returns API info', async () => {
22
+ const response = await request(app).get('/');
23
+
24
+ expect(response.status).toBe(200);
25
+ expect(response.body.message).toBe('API is running');
26
+ expect(response.body.endpoints).toBeDefined();
27
+ });
28
+
29
+ test('GET /health returns { ok: true }', async () => {
30
+ const response = await request(app).get('/health');
31
+
32
+ expect(response.status).toBe(200);
33
+ expect(response.body).toEqual({ ok: true });
34
+ });
35
+
36
+ test('GET /api/users returns an array', async () => {
37
+ const response = await request(app).get('/api/users');
38
+
39
+ expect(response.status).toBe(200);
40
+ expect(Array.isArray(response.body)).toBe(true);
41
+ });
42
+
43
+ test('POST /api/users with missing name returns 400', async () => {
44
+ const response = await request(app)
45
+ .post('/api/users')
46
+ .send({ email: 'test@example.com' });
47
+
48
+ expect(response.status).toBe(400);
49
+ expect(response.body.message).toBeDefined();
50
+ });
51
+ });
@@ -0,0 +1,13 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16
4
+ environment:
5
+ POSTGRES_USER: postgres
6
+ POSTGRES_PASSWORD: postgres
7
+ POSTGRES_DB: <%= databaseName %>
8
+ ports:
9
+ - '5433:5432'
10
+ volumes:
11
+ - postgres_data:/var/lib/postgresql/data
12
+ volumes:
13
+ postgres_data:
@@ -0,0 +1,8 @@
1
+ <% if (educational) { %>-- File overview: Creates the tables and constraints required by this starter API.
2
+ <% } %>
3
+ DROP TABLE IF EXISTS users;
4
+ CREATE TABLE users (
5
+ id SERIAL PRIMARY KEY,
6
+ name TEXT NOT NULL,
7
+ email TEXT UNIQUE
8
+ );
@@ -0,0 +1,7 @@
1
+ <% if (educational) { %>-- File overview: Inserts starter rows so the API has sample data on first run.
2
+ <% } %>
3
+ INSERT INTO users (name, email)
4
+ VALUES
5
+ ('Ada Lovelace', 'ada@example.com'),
6
+ ('Grace Hopper', 'grace@example.com'),
7
+ ('Margaret Hamilton', 'margaret@example.com');
@@ -0,0 +1,6 @@
1
+ <% if (isEsm) { %>export default {
2
+ <% } else { %>module.exports = {
3
+ <% } %>
4
+ testEnvironment: 'node'<% if (isPostgres) { %>,
5
+ setupFiles: ['dotenv/config']<% } %>
6
+ };
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "<%= packageName %>",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ <% if (isEsm) { %> "type": "module",
6
+ <% } %>
7
+ "description": "Express API generated by @alexmc2/create-express-api-starter",
8
+ "scripts": {
9
+ "dev": "<%= jsDevCommand %>",
10
+ "start": "node src/server.js",
11
+ "test": "<% if (isEsm) { %>node --experimental-vm-modules ./node_modules/jest/bin/jest.js<% } else { %>jest<% } %>",
12
+ "lint": "eslint ."<% if (isPsql) { %>,
13
+ "db:create": "node scripts/dbCreate.js",
14
+ "db:setup": "node scripts/dbSetup.js",
15
+ "db:seed": "node scripts/dbSeed.js",
16
+ "db:reset": "node scripts/dbReset.js"<% } %><% if (isDocker) { %>,
17
+ "db:up": "docker compose up -d",
18
+ "db:down": "docker compose down -v",
19
+ "db:setup": "node scripts/dbSetup.js",
20
+ "db:seed": "node scripts/dbSeed.js",
21
+ "db:reset": "node scripts/dbReset.js"<% } %>
22
+ },
23
+ "engines": {
24
+ "node": "<%- useNodemon ? '>=20' : '>=20.13' %>"
25
+ },
26
+ "dependencies": {
27
+ "cors": "^2.8.5",
28
+ "dotenv": "^16.4.5",
29
+ "express": "^4.21.1",
30
+ "helmet": "^8.0.0",
31
+ "morgan": "^1.10.0"<% if (isPostgres) { %>,
32
+ "pg": "^8.13.1"<% } %>
33
+ },
34
+ "devDependencies": {
35
+ "eslint": "^8.57.0",
36
+ "jest": "^29.7.0",
37
+ <% if (useNodemon) { %> "nodemon": "^3.1.9",
38
+ <% } %> "supertest": "^7.0.0"
39
+ }
40
+ }
@@ -0,0 +1,97 @@
1
+ <% if (educational) { %>// File overview: Creates the target PostgreSQL database if it does not already exist.
2
+ <% } %>
3
+ <% if (isEsm) { %>import 'dotenv/config';
4
+
5
+ import { Pool } from 'pg';
6
+ <% } else { %>require('dotenv').config();
7
+
8
+ const { Pool } = require('pg');
9
+ <% } %>
10
+
11
+ async function run() {
12
+ const databaseUrl = process.env.DATABASE_URL;
13
+
14
+ if (!databaseUrl) {
15
+ console.error('Error: DATABASE_URL is not set.');
16
+ console.error('Make sure you have a .env file with DATABASE_URL defined.');
17
+ console.error('You can copy .env.example to .env to get started.');
18
+ process.exit(1);
19
+ }
20
+
21
+ <% if (educational) { %> // Extract the database name from DATABASE_URL.
22
+ // This is the path segment that appears after host and port.
23
+ <% } %> const url = new URL(databaseUrl);
24
+ const dbName = url.pathname.slice(1);
25
+
26
+ if (!dbName) {
27
+ console.error('Error: DATABASE_URL does not contain a database name.');
28
+ process.exit(1);
29
+ }
30
+
31
+ <% if (educational) { %> // Validate the database name before interpolation.
32
+ // SQL identifiers cannot use parameter placeholders, so we restrict
33
+ // allowed characters to a safe subset.
34
+ <% } %> if (!/^[a-zA-Z0-9_-]+$/.test(dbName)) {
35
+ console.error(`Error: Database name "${dbName}" contains invalid characters.`);
36
+ console.error('Use only letters, numbers, hyphens, and underscores.');
37
+ process.exit(1);
38
+ }
39
+
40
+ <% if (educational) { %> // Connect to the default "postgres" database, then create the target one.
41
+ <% } %> url.pathname = '/postgres';
42
+ const pool = new Pool({ connectionString: url.toString() });
43
+
44
+ try {
45
+ const result = await pool.query(
46
+ 'SELECT 1 FROM pg_database WHERE datname = $1',
47
+ [dbName]
48
+ );
49
+
50
+ if (result.rows.length > 0) {
51
+ console.log(`Database "${dbName}" already exists.`);
52
+ return;
53
+ }
54
+
55
+ await pool.query(`CREATE DATABASE "${dbName}"`);
56
+ console.log(`Database "${dbName}" created.`);
57
+ } finally {
58
+ await pool.end();
59
+ }
60
+ }
61
+
62
+ run().catch((error) => {
63
+ const message =
64
+ error && typeof error === 'object' && typeof error.message === 'string'
65
+ ? error.message
66
+ : typeof error === 'string'
67
+ ? error
68
+ : String(error);
69
+ const code =
70
+ error && typeof error === 'object' && typeof error.code === 'string'
71
+ ? error.code
72
+ : '';
73
+
74
+ console.error('Failed to create database:', message);
75
+ console.error('');
76
+
77
+ const authError = message.includes('password') || message.includes('SCRAM');
78
+ const connectionRefused =
79
+ message.includes('ECONNREFUSED') || code === 'ECONNREFUSED';
80
+
81
+ if (authError) {
82
+ console.error('Your DATABASE_URL is missing credentials or the password is wrong.');
83
+ console.error('Update DATABASE_URL in your .env file:');
84
+ console.error(' DATABASE_URL=postgres://USER:PASSWORD@localhost:5432/<%= databaseName %>');
85
+ } else if (connectionRefused) {
86
+ console.error('PostgreSQL is not running. Start it first:');
87
+ console.error(' Linux: sudo systemctl start postgresql');
88
+ console.error(' macOS: brew services start postgresql');
89
+ } else {
90
+ console.error('Common fixes:');
91
+ console.error(' - Make sure PostgreSQL is running');
92
+ console.error(' - Check that your user has permission to create databases');
93
+ console.error(' - Verify DATABASE_URL in your .env file is correct');
94
+ }
95
+
96
+ process.exit(1);
97
+ });