Skip to content

Commit 88866c7

Browse files
authored
Fix production DB connectivity by using Prisma Accelerate correctly (#23)
* fix: use Prisma Accelerate in production * chore: align Node support with Accelerate * fix: unblock CI lint and migration flow
1 parent 11fdcff commit 88866c7

14 files changed

Lines changed: 220 additions & 43 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/locations_api"
2+
DIRECT_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/locations_api"
23
PORT="8080"
34
PAGE_SIZE="10"

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
node-version:
17-
- '20.19.0'
18-
- '22'
17+
- '22.13.0'
1918
services:
2019
postgres:
2120
image: postgres:16

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Compatibility-first REST API for Tanzania location data backed by PostgreSQL and
1212

1313
## Requirements
1414

15-
- Node.js `>=20.19.0`
15+
- Node.js `22.13.0+`
1616
- pnpm `10.7.0+`
1717
- PostgreSQL `16+` recommended
1818

@@ -30,7 +30,11 @@ Compatibility-first REST API for Tanzania location data backed by PostgreSQL and
3030
cp .env.example .env
3131
```
3232

33-
3. Start PostgreSQL and update `DATABASE_URL` if needed.
33+
3. Start PostgreSQL and update your connection strings if needed.
34+
35+
- Local and test environments use a direct PostgreSQL `DATABASE_URL`.
36+
- Production uses a Prisma Accelerate `DATABASE_URL`.
37+
- If you run `pnpm db:migrate` against an Accelerate-backed environment, also provide `DIRECT_DATABASE_URL` so the migration bootstrap can talk to Postgres directly.
3438

3539
4. Apply the checked-in schema and seed deterministic fixture data.
3640

@@ -66,6 +70,7 @@ pnpm openapi:json
6670
- On a fresh database it bootstraps the historical `init` migration, marks that baseline as applied, and then deploys later migrations
6771
- On an existing database that already has the older Prisma migration history, it only applies the new additive migrations
6872
- Prefer `pnpm db:migrate` over calling `prisma migrate deploy` directly
73+
- `DATABASE_URL` may point at Prisma Accelerate in production, but `pnpm db:migrate` still requires a direct Postgres URL in `DIRECT_DATABASE_URL`
6974

7075
## Testing
7176

@@ -146,7 +151,7 @@ Additional filters:
146151
## Dependency Automation
147152

148153
- `.github/dependabot.yml` opens weekly update PRs for npm packages and GitHub Actions
149-
- `.github/workflows/ci.yml` validates every PR against Postgres on Node `20.19.0` and `22`
154+
- `.github/workflows/ci.yml` validates every PR against Postgres on Node `22.13.0`
150155

151156
## License
152157

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"main": "./dist/server.js",
77
"engines": {
8-
"node": ">=20.19.0"
8+
"node": "^22.13.0 || >=24.0.0"
99
},
1010
"scripts": {
1111
"dev": "tsx watch server.ts",
@@ -37,6 +37,7 @@
3737
"dependencies": {
3838
"@prisma/adapter-pg": "^7.5.0",
3939
"@prisma/client": "^7.5.0",
40+
"@prisma/extension-accelerate": "^3.0.1",
4041
"cors": "^2.8.6",
4142
"dotenv": "^17.3.1",
4243
"express": "^5.2.1",

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export default defineConfig({
88
seed: 'tsx prisma/seed.ts',
99
},
1010
datasource: {
11-
url: process.env.DATABASE_URL ?? 'postgresql://postgres:postgres@localhost:5432/locations_api',
11+
url:
12+
process.env.DIRECT_DATABASE_URL ??
13+
process.env.DATABASE_URL ??
14+
'postgresql://postgres:postgres@localhost:5432/locations_api',
1215
},
1316
});

prisma/migrations/20250411175910_cleanup/migration.sql

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@
1414
1515
*/
1616
-- AlterTable
17-
ALTER TABLE "districts" DROP COLUMN "properties_count",
18-
DROP COLUMN "view_count",
19-
DROP COLUMN "watcher_count";
17+
ALTER TABLE "districts" DROP COLUMN IF EXISTS "properties_count",
18+
DROP COLUMN IF EXISTS "view_count",
19+
DROP COLUMN IF EXISTS "watcher_count";
2020

2121
-- AlterTable
22-
ALTER TABLE "places" DROP COLUMN "properties_count",
23-
DROP COLUMN "view_count";
22+
ALTER TABLE "places" DROP COLUMN IF EXISTS "properties_count",
23+
DROP COLUMN IF EXISTS "view_count";
2424

2525
-- AlterTable
26-
ALTER TABLE "regions" DROP COLUMN "properties_count",
27-
DROP COLUMN "view_count",
28-
DROP COLUMN "watcher_count";
26+
ALTER TABLE "regions" DROP COLUMN IF EXISTS "properties_count",
27+
DROP COLUMN IF EXISTS "view_count",
28+
DROP COLUMN IF EXISTS "watcher_count";
2929

3030
-- AlterTable
31-
ALTER TABLE "wards" DROP COLUMN "properties_count",
32-
DROP COLUMN "view_count";
31+
ALTER TABLE "wards" DROP COLUMN IF EXISTS "properties_count",
32+
DROP COLUMN IF EXISTS "view_count";

scripts/migrate.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { Pool } from 'pg';
33
import config from '../src/config.js';
44

55
const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
6+
const directDatabaseUrl = config.directDatabaseUrl;
7+
8+
if (!directDatabaseUrl) {
9+
throw new Error('db:migrate requires DIRECT_DATABASE_URL when DATABASE_URL uses Prisma Accelerate.');
10+
}
611

712
function runPrisma(args: string[]) {
813
const result = spawnSync(
@@ -21,7 +26,7 @@ function runPrisma(args: string[]) {
2126

2227
async function bootstrapIfNeeded() {
2328
const pool = new Pool({
24-
connectionString: config.databaseUrl,
29+
connectionString: directDatabaseUrl,
2530
});
2631

2732
try {

server.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,50 @@
11
import app from './src/app.js';
22
import config from './src/config.js';
3-
import { disconnectPrisma } from './src/db/prisma.js';
4-
5-
const server = app.listen(config.port, () => {
6-
console.log(
7-
JSON.stringify({
8-
environment: config.nodeEnv,
9-
message: 'Server started',
10-
openApiUrl: `http://localhost:${config.port}/openapi.json`,
11-
port: config.port,
12-
swaggerUrl: `http://localhost:${config.port}/api-docs`,
13-
}),
14-
);
15-
});
3+
import { checkDatabaseConnection, disconnectPrisma } from './src/db/prisma.js';
4+
5+
let server: ReturnType<typeof app.listen> | undefined;
6+
7+
async function startServer() {
8+
const database = await checkDatabaseConnection();
9+
10+
if (!database.ok) {
11+
console.error(
12+
JSON.stringify({
13+
error: database.error,
14+
message: 'Database readiness check failed. Refusing to start server.',
15+
}),
16+
);
17+
process.exit(1);
18+
}
19+
20+
server = app.listen(config.port, () => {
21+
console.log(
22+
JSON.stringify({
23+
environment: config.nodeEnv,
24+
message: 'Server started',
25+
openApiUrl: `http://localhost:${config.port}/openapi.json`,
26+
port: config.port,
27+
swaggerUrl: `http://localhost:${config.port}/api-docs`,
28+
}),
29+
);
30+
});
31+
}
1632

1733
async function shutdown(signal: NodeJS.Signals) {
1834
console.log(JSON.stringify({ message: 'Graceful shutdown requested', signal }));
1935

36+
if (!server) {
37+
void disconnectPrisma()
38+
.then(() => {
39+
process.exit(0);
40+
})
41+
.catch((error: unknown) => {
42+
console.error(JSON.stringify({ error, message: 'Failed to disconnect Prisma cleanly' }));
43+
process.exit(1);
44+
});
45+
return;
46+
}
47+
2048
server.close(() => {
2149
void disconnectPrisma()
2250
.then(() => {
@@ -36,3 +64,5 @@ process.on('SIGINT', () => {
3664
process.on('SIGTERM', () => {
3765
void shutdown('SIGTERM');
3866
});
67+
68+
await startServer();

src/app.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import helmet from 'helmet';
44
import morgan from 'morgan';
55
import type { Request, Response } from 'express';
66
import config from './config.js';
7+
import { checkDatabaseConnection } from './db/prisma.js';
78
import { setupSwagger } from './docs/swagger.js';
89
import { errorHandler } from './middleware/errorHandler.js';
910
import {
@@ -36,12 +37,15 @@ app.use(morgan(logFormatter));
3637
app.use(express.json());
3738
app.use(express.urlencoded({ extended: true }));
3839

39-
app.get('/health', (_: Request, res: Response) => {
40-
res.status(200).json({
41-
status: 'UP',
42-
timestamp: new Date().toISOString(),
40+
app.get('/health', async (_: Request, res: Response) => {
41+
const database = await checkDatabaseConnection({ logErrors: false });
42+
43+
res.status(database.ok ? 200 : 503).json({
44+
database: database.ok ? 'UP' : 'DOWN',
4345
environment: config.nodeEnv,
44-
version: process.env.npm_package_version || '1.0.0'
46+
status: database.ok ? 'UP' : 'DEGRADED',
47+
timestamp: new Date().toISOString(),
48+
version: process.env.npm_package_version || '1.0.0',
4549
});
4650
});
4751

0 commit comments

Comments
 (0)