@alexmc2/create-express-api-starter 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +38 -8
  2. package/dist/cli.js +80 -29
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +14 -10
  5. package/templates/js/mvc/package.json.ejs +10 -10
  6. package/templates/js/simple/package.json.ejs +10 -10
  7. package/templates/ts/shared/.eslintrc.cjs.ejs +39 -0
  8. package/templates/ts/{mvc → shared}/package.json.ejs +22 -22
  9. package/templates/ts/shared/tsconfig.eslint.json.ejs +8 -0
  10. package/templates/ts/mvc/.eslintrc.cjs.ejs +0 -27
  11. package/templates/ts/simple/.env.example.ejs +0 -7
  12. package/templates/ts/simple/.eslintrc.cjs.ejs +0 -27
  13. package/templates/ts/simple/.gitignore.ejs +0 -6
  14. package/templates/ts/simple/__tests__/app.test.ts.ejs +0 -45
  15. package/templates/ts/simple/compose.yaml.ejs +0 -13
  16. package/templates/ts/simple/db/schema.sql.ejs +0 -8
  17. package/templates/ts/simple/db/seed.sql.ejs +0 -7
  18. package/templates/ts/simple/jest.config.js.ejs +0 -7
  19. package/templates/ts/simple/package.json.ejs +0 -51
  20. package/templates/ts/simple/scripts/dbCreate.js.ejs +0 -93
  21. package/templates/ts/simple/scripts/dbReset.js.ejs +0 -40
  22. package/templates/ts/simple/scripts/dbSeed.js.ejs +0 -62
  23. package/templates/ts/simple/scripts/dbSetup.js.ejs +0 -62
  24. package/templates/ts/simple/src/app.ts.ejs +0 -45
  25. package/templates/ts/simple/src/db/pool.ts.ejs +0 -17
  26. package/templates/ts/simple/src/errors/AppError.ts.ejs +0 -14
  27. package/templates/ts/simple/src/middleware/errorHandler.ts.ejs +0 -49
  28. package/templates/ts/simple/src/middleware/notFound.ts.ejs +0 -13
  29. package/templates/ts/simple/src/routes/health.ts.ejs +0 -13
  30. package/templates/ts/simple/src/server.ts.ejs +0 -15
  31. package/templates/ts/simple/src/utils/getPort.ts.ejs +0 -12
  32. package/templates/ts/simple/tsconfig.json.ejs +0 -13
  33. /package/templates/ts/{mvc → shared}/.env.example.ejs +0 -0
  34. /package/templates/ts/{mvc → shared}/.gitignore.ejs +0 -0
  35. /package/templates/ts/{mvc → shared}/__tests__/app.test.ts.ejs +0 -0
  36. /package/templates/ts/{mvc → shared}/compose.yaml.ejs +0 -0
  37. /package/templates/ts/{mvc → shared}/db/schema.sql.ejs +0 -0
  38. /package/templates/ts/{mvc → shared}/db/seed.sql.ejs +0 -0
  39. /package/templates/ts/{mvc → shared}/jest.config.js.ejs +0 -0
  40. /package/templates/ts/{mvc → shared}/scripts/dbCreate.js.ejs +0 -0
  41. /package/templates/ts/{mvc → shared}/scripts/dbReset.js.ejs +0 -0
  42. /package/templates/ts/{mvc → shared}/scripts/dbSeed.js.ejs +0 -0
  43. /package/templates/ts/{mvc → shared}/scripts/dbSetup.js.ejs +0 -0
  44. /package/templates/ts/{mvc → shared}/src/app.ts.ejs +0 -0
  45. /package/templates/ts/{mvc → shared}/src/db/pool.ts.ejs +0 -0
  46. /package/templates/ts/{mvc → shared}/src/errors/AppError.ts.ejs +0 -0
  47. /package/templates/ts/{mvc → shared}/src/middleware/errorHandler.ts.ejs +0 -0
  48. /package/templates/ts/{mvc → shared}/src/middleware/notFound.ts.ejs +0 -0
  49. /package/templates/ts/{mvc → shared}/src/routes/health.ts.ejs +0 -0
  50. /package/templates/ts/{mvc → shared}/src/server.ts.ejs +0 -0
  51. /package/templates/ts/{mvc → shared}/src/utils/getPort.ts.ejs +0 -0
  52. /package/templates/ts/{mvc → shared}/tsconfig.json.ejs +0 -0
@@ -24,17 +24,17 @@
24
24
  "node": "<%- useNodemon ? '>=20' : '>=20.13' %>"
25
25
  },
26
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"<% } %>
27
+ "cors": "^2.8.6",
28
+ "dotenv": "^17.3.1",
29
+ "express": "^5.2.1",
30
+ "helmet": "^8.1.0",
31
+ "morgan": "^1.10.1"<% if (isPostgres) { %>,
32
+ "pg": "^8.18.0"<% } %>
33
33
  },
34
34
  "devDependencies": {
35
- "eslint": "^8.57.0",
36
- "jest": "^29.7.0",
37
- <% if (useNodemon) { %> "nodemon": "^3.1.9",
38
- <% } %> "supertest": "^7.0.0"
35
+ "eslint": "^8.57.1",
36
+ "jest": "^30.2.0",
37
+ <% if (useNodemon) { %> "nodemon": "^3.1.11",
38
+ <% } %> "supertest": "^7.2.2"
39
39
  }
40
40
  }
@@ -24,17 +24,17 @@
24
24
  "node": "<%- useNodemon ? '>=20' : '>=20.13' %>"
25
25
  },
26
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"<% } %>
27
+ "cors": "^2.8.6",
28
+ "dotenv": "^17.3.1",
29
+ "express": "^5.2.1",
30
+ "helmet": "^8.1.0",
31
+ "morgan": "^1.10.1"<% if (isPostgres) { %>,
32
+ "pg": "^8.18.0"<% } %>
33
33
  },
34
34
  "devDependencies": {
35
- "eslint": "^8.57.0",
36
- "jest": "^29.7.0",
37
- <% if (useNodemon) { %> "nodemon": "^3.1.9",
38
- <% } %> "supertest": "^7.0.0"
35
+ "eslint": "^8.57.1",
36
+ "jest": "^30.2.0",
37
+ <% if (useNodemon) { %> "nodemon": "^3.1.11",
38
+ <% } %> "supertest": "^7.2.2"
39
39
  }
40
40
  }
@@ -0,0 +1,39 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: {
4
+ node: true,
5
+ es2022: true,
6
+ jest: true
7
+ },
8
+ extends: ['eslint:recommended'],
9
+ ignorePatterns: ['dist/', 'node_modules/', 'coverage/'],
10
+ rules: {
11
+ 'no-control-regex': 'off'
12
+ },
13
+ overrides: [
14
+ {
15
+ files: ['**/*.ts'],
16
+ parser: '@typescript-eslint/parser',
17
+ parserOptions: {
18
+ ecmaVersion: 'latest',
19
+ sourceType: 'module',
20
+ project: ['./tsconfig.eslint.json'],
21
+ tsconfigRootDir: __dirname
22
+ },
23
+ plugins: ['@typescript-eslint'],
24
+ extends: ['plugin:@typescript-eslint/recommended'],
25
+ rules: {
26
+ 'no-undef': 'off',
27
+ 'no-unused-vars': 'off',
28
+ '@typescript-eslint/await-thenable': 'error',
29
+ '@typescript-eslint/no-unused-vars': [
30
+ 'error',
31
+ {
32
+ argsIgnorePattern: '^_',
33
+ varsIgnorePattern: '^_'
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ]
39
+ };
@@ -23,29 +23,29 @@
23
23
  "node": ">=20.13"
24
24
  },
25
25
  "dependencies": {
26
- "cors": "^2.8.5",
27
- "dotenv": "^16.4.5",
28
- "express": "^4.21.1",
29
- "helmet": "^8.0.0",
30
- "morgan": "^1.10.0"<% if (isPostgres) { %>,
31
- "pg": "^8.13.1"<% } %>
26
+ "cors": "^2.8.6",
27
+ "dotenv": "^17.3.1",
28
+ "express": "^5.2.1",
29
+ "helmet": "^8.1.0",
30
+ "morgan": "^1.10.1"<% if (isPostgres) { %>,
31
+ "pg": "^8.18.0"<% } %>
32
32
  },
33
33
  "devDependencies": {
34
- "@swc/core": "^1.7.26",
35
- "@swc/jest": "^0.2.36",
36
- "@types/cors": "^2.8.17",
37
- "@types/express": "^5.0.0",
38
- "@types/jest": "^29.5.13",
39
- "@types/morgan": "^1.9.9",
40
- "@types/node": "^20.16.11",
41
- <% if (isPostgres) { %> "@types/pg": "^8.11.10",
42
- <% } %> "@types/supertest": "^6.0.2",
43
- "@typescript-eslint/eslint-plugin": "^7.18.0",
44
- "@typescript-eslint/parser": "^7.18.0",
45
- "eslint": "^8.57.0",
46
- "jest": "^29.7.0",
47
- "supertest": "^7.0.0",
48
- "tsx": "^4.19.2",
49
- "typescript": "^5.6.3"
34
+ "@swc/core": "^1.15.11",
35
+ "@swc/jest": "^0.2.39",
36
+ "@types/cors": "^2.8.19",
37
+ "@types/express": "^5.0.6",
38
+ "@types/jest": "^30.0.0",
39
+ "@types/morgan": "^1.9.10",
40
+ "@types/node": "^20.19.33",
41
+ <% if (isPostgres) { %> "@types/pg": "^8.16.0",
42
+ <% } %> "@types/supertest": "^6.0.3",
43
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
44
+ "@typescript-eslint/parser": "^8.56.0",
45
+ "eslint": "^8.57.1",
46
+ "jest": "^30.2.0",
47
+ "supertest": "^7.2.2",
48
+ "tsx": "^4.21.0",
49
+ "typescript": "^5.9.3"
50
50
  }
51
51
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "noEmit": true
6
+ },
7
+ "include": ["src", "__tests__"]
8
+ }
@@ -1,27 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- env: {
4
- node: true,
5
- es2022: true,
6
- jest: true
7
- },
8
- parser: '@typescript-eslint/parser',
9
- parserOptions: {
10
- ecmaVersion: 'latest',
11
- sourceType: 'module'
12
- },
13
- plugins: ['@typescript-eslint'],
14
- extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
15
- ignorePatterns: ['dist/', 'node_modules/', 'coverage/'],
16
- rules: {
17
- 'no-undef': 'off',
18
- 'no-unused-vars': 'off',
19
- '@typescript-eslint/no-unused-vars': [
20
- 'error',
21
- {
22
- argsIgnorePattern: '^_',
23
- varsIgnorePattern: '^_'
24
- }
25
- ]
26
- }
27
- };
@@ -1,7 +0,0 @@
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
- <% } %>
@@ -1,27 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- env: {
4
- node: true,
5
- es2022: true,
6
- jest: true
7
- },
8
- parser: '@typescript-eslint/parser',
9
- parserOptions: {
10
- ecmaVersion: 'latest',
11
- sourceType: 'module'
12
- },
13
- plugins: ['@typescript-eslint'],
14
- extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
15
- ignorePatterns: ['dist/', 'node_modules/', 'coverage/'],
16
- rules: {
17
- 'no-undef': 'off',
18
- 'no-unused-vars': 'off',
19
- '@typescript-eslint/no-unused-vars': [
20
- 'error',
21
- {
22
- argsIgnorePattern: '^_',
23
- varsIgnorePattern: '^_'
24
- }
25
- ]
26
- }
27
- };
@@ -1,6 +0,0 @@
1
- node_modules
2
- .env
3
- dist
4
- coverage
5
- npm-debug.log*
6
- .DS_Store
@@ -1,45 +0,0 @@
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
- <% } %>import request from 'supertest';
6
-
7
- import app from '../src/app';
8
- <% if (isPostgres) { %>import pool from '../src/db/pool';
9
- <% } %>
10
- <% if (isPostgres) { %>afterAll(async () => {
11
- await pool.end();
12
- });
13
-
14
- <% } %>describe('API', () => {
15
- test('GET / returns API info', async () => {
16
- const response = await request(app).get('/');
17
-
18
- expect(response.status).toBe(200);
19
- expect(response.body.message).toBe('API is running');
20
- expect(response.body.endpoints).toBeDefined();
21
- });
22
-
23
- test('GET /health returns { ok: true }', async () => {
24
- const response = await request(app).get('/health');
25
-
26
- expect(response.status).toBe(200);
27
- expect(response.body).toEqual({ ok: true });
28
- });
29
-
30
- test('GET /api/users returns an array', async () => {
31
- const response = await request(app).get('/api/users');
32
-
33
- expect(response.status).toBe(200);
34
- expect(Array.isArray(response.body)).toBe(true);
35
- });
36
-
37
- test('POST /api/users with missing name returns 400', async () => {
38
- const response = await request(app)
39
- .post('/api/users')
40
- .send({ email: 'test@example.com' });
41
-
42
- expect(response.status).toBe(400);
43
- expect(response.body.message).toBeDefined();
44
- });
45
- });
@@ -1,13 +0,0 @@
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:
@@ -1,8 +0,0 @@
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
- );
@@ -1,7 +0,0 @@
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');
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- transform: {
3
- '^.+\\.tsx?$': ['@swc/jest']
4
- },
5
- testEnvironment: 'node'<% if (isPostgres) { %>,
6
- setupFiles: ['dotenv/config']<% } %>
7
- };
@@ -1,51 +0,0 @@
1
- {
2
- "name": "<%= packageName %>",
3
- "version": "0.1.0",
4
- "private": true,
5
- "description": "Express API generated by @alexmc2/create-express-api-starter",
6
- "scripts": {
7
- "dev": "tsx watch src/server.ts",
8
- "build": "tsc",
9
- "start": "node dist/server.js",
10
- "test": "jest",
11
- "lint": "eslint . --ext .ts"<% if (isPsql) { %>,
12
- "db:create": "node scripts/dbCreate.js",
13
- "db:setup": "node scripts/dbSetup.js",
14
- "db:seed": "node scripts/dbSeed.js",
15
- "db:reset": "node scripts/dbReset.js"<% } %><% if (isDocker) { %>,
16
- "db:up": "docker compose up -d",
17
- "db:down": "docker compose down -v",
18
- "db:setup": "node scripts/dbSetup.js",
19
- "db:seed": "node scripts/dbSeed.js",
20
- "db:reset": "node scripts/dbReset.js"<% } %>
21
- },
22
- "engines": {
23
- "node": ">=20.13"
24
- },
25
- "dependencies": {
26
- "cors": "^2.8.5",
27
- "dotenv": "^16.4.5",
28
- "express": "^4.21.1",
29
- "helmet": "^8.0.0",
30
- "morgan": "^1.10.0"<% if (isPostgres) { %>,
31
- "pg": "^8.13.1"<% } %>
32
- },
33
- "devDependencies": {
34
- "@swc/core": "^1.7.26",
35
- "@swc/jest": "^0.2.36",
36
- "@types/cors": "^2.8.17",
37
- "@types/express": "^5.0.0",
38
- "@types/jest": "^29.5.13",
39
- "@types/morgan": "^1.9.9",
40
- "@types/node": "^20.16.11",
41
- <% if (isPostgres) { %> "@types/pg": "^8.11.10",
42
- <% } %> "@types/supertest": "^6.0.2",
43
- "@typescript-eslint/eslint-plugin": "^7.18.0",
44
- "@typescript-eslint/parser": "^7.18.0",
45
- "eslint": "^8.57.0",
46
- "jest": "^29.7.0",
47
- "supertest": "^7.0.0",
48
- "tsx": "^4.19.2",
49
- "typescript": "^5.6.3"
50
- }
51
- }
@@ -1,93 +0,0 @@
1
- <% if (educational) { %>// File overview: Creates the target PostgreSQL database if it does not already exist.
2
- <% } %>
3
- require('dotenv').config();
4
-
5
- const { Pool } = require('pg');
6
-
7
- async function run() {
8
- const databaseUrl = process.env.DATABASE_URL;
9
-
10
- if (!databaseUrl) {
11
- console.error('Error: DATABASE_URL is not set.');
12
- console.error('Make sure you have a .env file with DATABASE_URL defined.');
13
- console.error('You can copy .env.example to .env to get started.');
14
- process.exit(1);
15
- }
16
-
17
- <% if (educational) { %> // Extract the database name from DATABASE_URL.
18
- // This is the path segment that appears after host and port.
19
- <% } %> const url = new URL(databaseUrl);
20
- const dbName = url.pathname.slice(1);
21
-
22
- if (!dbName) {
23
- console.error('Error: DATABASE_URL does not contain a database name.');
24
- process.exit(1);
25
- }
26
-
27
- <% if (educational) { %> // Validate the database name before interpolation.
28
- // SQL identifiers cannot use parameter placeholders, so we restrict
29
- // allowed characters to a safe subset.
30
- <% } %> if (!/^[a-zA-Z0-9_-]+$/.test(dbName)) {
31
- console.error(`Error: Database name "${dbName}" contains invalid characters.`);
32
- console.error('Use only letters, numbers, hyphens, and underscores.');
33
- process.exit(1);
34
- }
35
-
36
- <% if (educational) { %> // Connect to the default "postgres" database, then create the target one.
37
- <% } %> url.pathname = '/postgres';
38
- const pool = new Pool({ connectionString: url.toString() });
39
-
40
- try {
41
- const result = await pool.query(
42
- 'SELECT 1 FROM pg_database WHERE datname = $1',
43
- [dbName]
44
- );
45
-
46
- if (result.rows.length > 0) {
47
- console.log(`Database "${dbName}" already exists.`);
48
- return;
49
- }
50
-
51
- await pool.query(`CREATE DATABASE "${dbName}"`);
52
- console.log(`Database "${dbName}" created.`);
53
- } finally {
54
- await pool.end();
55
- }
56
- }
57
-
58
- run().catch((error) => {
59
- const message =
60
- error && typeof error === 'object' && typeof error.message === 'string'
61
- ? error.message
62
- : typeof error === 'string'
63
- ? error
64
- : String(error);
65
- const code =
66
- error && typeof error === 'object' && typeof error.code === 'string'
67
- ? error.code
68
- : '';
69
-
70
- console.error('Failed to create database:', message);
71
- console.error('');
72
-
73
- const authError = message.includes('password') || message.includes('SCRAM');
74
- const connectionRefused =
75
- message.includes('ECONNREFUSED') || code === 'ECONNREFUSED';
76
-
77
- if (authError) {
78
- console.error('Your DATABASE_URL is missing credentials or the password is wrong.');
79
- console.error('Update DATABASE_URL in your .env file:');
80
- console.error(' DATABASE_URL=postgres://USER:PASSWORD@localhost:5432/<%= databaseName %>');
81
- } else if (connectionRefused) {
82
- console.error('PostgreSQL is not running. Start it first:');
83
- console.error(' Linux: sudo systemctl start postgresql');
84
- console.error(' macOS: brew services start postgresql');
85
- } else {
86
- console.error('Common fixes:');
87
- console.error(' - Make sure PostgreSQL is running');
88
- console.error(' - Check that your user has permission to create databases');
89
- console.error(' - Verify DATABASE_URL in your .env file is correct');
90
- }
91
-
92
- process.exit(1);
93
- });
@@ -1,40 +0,0 @@
1
- <% if (educational) { %>// File overview: Executes the reset workflow by running setup and seed scripts in order.
2
- <% } %>
3
- const { spawn } = require('node:child_process');
4
-
5
- <% if (educational) { %>// Run an npm script and stream output to this terminal.
6
- // Wrapping spawn in a Promise lets us await each step in order.
7
- <% } %>function run(command, args) {
8
- return new Promise((resolve, reject) => {
9
- const child = spawn(command, args, {
10
- stdio: 'inherit',
11
- shell: true
12
- });
13
-
14
- child.on('exit', (code) => {
15
- if (code === 0) {
16
- resolve();
17
- return;
18
- }
19
-
20
- reject(new Error(`${command} ${args.join(' ')} failed with exit code ${code}`));
21
- });
22
-
23
- child.on('error', reject);
24
- });
25
- }
26
-
27
- async function main() {
28
- <% if (isDocker) { %><% if (educational) { %> // Restart the container so reset starts from a clean database state.
29
- <% } %> await run('npm', ['run', 'db:down']);
30
- await run('npm', ['run', 'db:up']);
31
- <% } %><% if (educational) { %> // Recreate tables first, then insert the sample data.
32
- <% } %> await run('npm', ['run', 'db:setup']);
33
- await run('npm', ['run', 'db:seed']);
34
- }
35
-
36
- main().catch((error) => {
37
- <% if (educational) { %> // Child scripts print detailed logs, so this is a short summary.
38
- <% } %> console.error(error.message);
39
- process.exit(1);
40
- });
@@ -1,62 +0,0 @@
1
- <% if (educational) { %>// File overview: Runs db/seed.sql to insert starter rows into the database.
2
- <% } %>
3
- require('dotenv').config();
4
-
5
- const fs = require('node:fs/promises');
6
- const path = require('node:path');
7
- const { Pool } = require('pg');
8
- <% if (isDocker) { %>
9
- const RETRIES = 20;
10
- const RETRY_DELAY_MS = 1000;
11
-
12
- <% if (educational) { %>// Sleep helper used between retry attempts.
13
- <% } %>function sleep(ms) {
14
- return new Promise((resolve) => setTimeout(resolve, ms));
15
- }
16
-
17
- <% if (educational) { %>// Retry a simple query until PostgreSQL is ready.
18
- <% } %>async function waitForDatabase(pool) {
19
- for (let attempt = 1; attempt <= RETRIES; attempt += 1) {
20
- try {
21
- await pool.query('SELECT 1');
22
- return;
23
- } catch {
24
- console.log('Waiting for database...');
25
- await sleep(RETRY_DELAY_MS);
26
- }
27
- }
28
-
29
- throw new Error('Unable to connect to database after 20 retries.');
30
- }
31
- <% } %>
32
- <% if (educational) { %>// Read db/seed.sql and run it to insert starter data.
33
- <% } %>async function run() {
34
- const databaseUrl = process.env.DATABASE_URL;
35
-
36
- if (!databaseUrl) {
37
- console.error('Error: DATABASE_URL is not set.');
38
- console.error('Make sure you have a .env file with DATABASE_URL defined.');
39
- console.error('You can copy .env.example to .env to get started.');
40
- process.exit(1);
41
- }
42
-
43
- const pool = new Pool({ connectionString: databaseUrl });
44
-
45
- try {
46
- <% if (isDocker) { %><% if (educational) { %> // Docker containers may need a few seconds before accepting connections.
47
- <% } %> await waitForDatabase(pool);
48
- <% } %><% if (educational) { %> // Loading SQL from a file keeps sample data updates easy to review.
49
- <% } %> const seedPath = path.join(process.cwd(), 'db', 'seed.sql');
50
- const seedSql = await fs.readFile(seedPath, 'utf8');
51
- await pool.query(seedSql);
52
- console.log('Database seed applied.');
53
- } finally {
54
- await pool.end();
55
- }
56
- }
57
-
58
- run().catch((error) => {
59
- <% if (educational) { %> // Exit with a non-zero status so npm reports the script as failed.
60
- <% } %> console.error(error.message);
61
- process.exit(1);
62
- });
@@ -1,62 +0,0 @@
1
- <% if (educational) { %>// File overview: Runs db/schema.sql to create or reset database tables.
2
- <% } %>
3
- require('dotenv').config();
4
-
5
- const fs = require('node:fs/promises');
6
- const path = require('node:path');
7
- const { Pool } = require('pg');
8
- <% if (isDocker) { %>
9
- const RETRIES = 20;
10
- const RETRY_DELAY_MS = 1000;
11
-
12
- <% if (educational) { %>// Sleep helper used between retry attempts while the container starts.
13
- <% } %>function sleep(ms) {
14
- return new Promise((resolve) => setTimeout(resolve, ms));
15
- }
16
-
17
- <% if (educational) { %>// Retry a simple query until PostgreSQL is ready to accept commands.
18
- <% } %>async function waitForDatabase(pool) {
19
- for (let attempt = 1; attempt <= RETRIES; attempt += 1) {
20
- try {
21
- await pool.query('SELECT 1');
22
- return;
23
- } catch {
24
- console.log('Waiting for database...');
25
- await sleep(RETRY_DELAY_MS);
26
- }
27
- }
28
-
29
- throw new Error('Unable to connect to database after 20 retries.');
30
- }
31
- <% } %>
32
- <% if (educational) { %>// Read db/schema.sql and apply it to the configured database.
33
- <% } %>async function run() {
34
- const databaseUrl = process.env.DATABASE_URL;
35
-
36
- if (!databaseUrl) {
37
- console.error('Error: DATABASE_URL is not set.');
38
- console.error('Make sure you have a .env file with DATABASE_URL defined.');
39
- console.error('You can copy .env.example to .env to get started.');
40
- process.exit(1);
41
- }
42
-
43
- const pool = new Pool({ connectionString: databaseUrl });
44
-
45
- try {
46
- <% if (isDocker) { %><% if (educational) { %> // Docker containers may need a few seconds before accepting connections.
47
- <% } %> await waitForDatabase(pool);
48
- <% } %><% if (educational) { %> // Loading SQL from a file keeps schema changes visible in version control.
49
- <% } %> const schemaPath = path.join(process.cwd(), 'db', 'schema.sql');
50
- const schemaSql = await fs.readFile(schemaPath, 'utf8');
51
- await pool.query(schemaSql);
52
- console.log('Database schema applied.');
53
- } finally {
54
- await pool.end();
55
- }
56
- }
57
-
58
- run().catch((error) => {
59
- <% if (educational) { %> // Exit with a non-zero status so npm reports the script as failed.
60
- <% } %> console.error(error.message);
61
- process.exit(1);
62
- });