Skip to content

Commit eb278ed

Browse files
maotoraAnoRebel
andauthored
Modernize the API runtime with Prisma 7 and automated updates (#15)
* chore: migrate runtime to Prisma 7 * docs: refresh setup and API usage * ci: add dependency update and verification workflows * fix: preserve migration compatibility and seed safety * docs: expand migration and testing guidance * fix: remove 'as const', improve search query safety and clarity - Remove unnecessary 'as const' assertion from mode: 'insensitive' in contains() - Strip null bytes from search input before passing to plainto_tsquery - Refactor search SQL to use a CTE so plainto_tsquery is evaluated once - Move seed destructiveness warning to step 4 where db:seed is first introduced * fix: resolve no-unsafe-call lint error in search route String(q) avoids calling .replace() on an any-typed value from req.validatedQuery, satisfying @typescript-eslint/no-unsafe-call which is enabled via recommendedTypeChecked but was not disabled in eslint.config.js. * fix: restore 'as const' on mode: 'insensitive' for Prisma type compatibility The 'as const' assertion is required so TypeScript narrows the string literal to the QueryMode union expected by Prisma's StringNullableFilter. Without it, mode is typed as plain 'string' which is not assignable. --------- Co-authored-by: Ano Rebel <hacker4rebel@gmail.com>
1 parent e88a81f commit eb278ed

32 files changed

Lines changed: 3556 additions & 1078 deletions

.env.example

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

.eslintrc.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

.github/dependabot.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
day: monday
8+
time: '06:00'
9+
timezone: Africa/Dar_es_Salaam
10+
open-pull-requests-limit: 10
11+
groups:
12+
npm-minor-patch:
13+
patterns:
14+
- '*'
15+
update-types:
16+
- minor
17+
- patch
18+
19+
- package-ecosystem: github-actions
20+
directory: /
21+
schedule:
22+
interval: weekly
23+
day: monday
24+
time: '06:15'
25+
timezone: Africa/Dar_es_Salaam
26+
open-pull-requests-limit: 5
27+
groups:
28+
github-actions-minor-patch:
29+
patterns:
30+
- '*'
31+
update-types:
32+
- minor
33+
- patch

.github/workflows/ci.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
- master
9+
10+
jobs:
11+
verify:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
node-version:
17+
- '20.19.0'
18+
- '22'
19+
services:
20+
postgres:
21+
image: postgres:16
22+
env:
23+
POSTGRES_DB: locations_api
24+
POSTGRES_PASSWORD: postgres
25+
POSTGRES_USER: postgres
26+
options: >-
27+
--health-cmd="pg_isready -U postgres -d locations_api"
28+
--health-interval=10s
29+
--health-timeout=5s
30+
--health-retries=5
31+
ports:
32+
- 5432:5432
33+
env:
34+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/locations_api
35+
NODE_ENV: test
36+
steps:
37+
- name: Checkout repository
38+
uses: actions/checkout@v4
39+
40+
- name: Setup pnpm
41+
uses: pnpm/action-setup@v4
42+
with:
43+
version: 10.7.0
44+
45+
- name: Setup Node.js
46+
uses: actions/setup-node@v4
47+
with:
48+
node-version: ${{ matrix.node-version }}
49+
cache: pnpm
50+
51+
- name: Install dependencies
52+
run: pnpm install --frozen-lockfile
53+
54+
- name: Lint, typecheck, and build
55+
run: pnpm build:ci
56+
57+
- name: Run migrations
58+
run: pnpm db:migrate
59+
60+
- name: Seed database
61+
run: pnpm db:seed
62+
63+
- name: Run tests
64+
run: pnpm test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
/dist
33
/node_modules
44
/prisma/node_modules
5+
/src/generated
6+
/generated
7+
/coverage
58

69
# Logs
710
logs

README.md

Lines changed: 103 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,153 @@
11
# Location Data API
22

3-
A RESTful API for Tanzania location data including countries, regions, districts, wards, and places.
3+
Compatibility-first REST API for Tanzania location data backed by PostgreSQL and Prisma ORM 7.
44

5-
## Features
5+
## What Changed
66

7-
- Hierarchical location data with proper relationships
8-
- RESTful API with clean structure
9-
- Input validation and error handling
10-
- Pagination and search support
7+
- Prisma ORM 7 with a generated client in `src/generated/prisma`
8+
- Dual API base paths: `/v1` is canonical and `/api` remains as a compatibility alias
9+
- Reproducible Prisma migration + seed flow for local development and CI
10+
- Request IDs, structured HTTP logs, cache headers, and safer full-text search
11+
- Dependabot and GitHub Actions for ongoing dependency updates and verification
1112

12-
## Tech Stack
13+
## Requirements
1314

14-
- Node.js / Express
15-
- PostgreSQL
16-
- Prisma ORM
17-
- Jest & Supertest for testing
15+
- Node.js `>=20.19.0`
16+
- pnpm `10.7.0+`
17+
- PostgreSQL `16+` recommended
1818

19-
## Getting Started
19+
## Quick Start
2020

21-
### Prerequisites
21+
1. Install dependencies.
2222

23-
- Node.js LTS
24-
- [Tanzania Locations Database](https://github.com/HackEAC/tanzania-locations-db) running 🏃🏿‍♂️🏃🏿‍♀️
25-
- npm or yarn
26-
27-
### Installation
23+
```bash
24+
pnpm install
25+
```
2826

29-
1. Clone the repository
27+
2. Create your environment file.
3028

3129
```bash
32-
git clone https://github.com/yourusername/locations-API.git
33-
cd locations-API
30+
cp .env.example .env
3431
```
3532

36-
2. Install dependencies
33+
3. Start PostgreSQL and update `DATABASE_URL` if needed.
34+
35+
4. Apply the checked-in schema and seed deterministic fixture data.
3736

3837
```bash
39-
npm install
38+
pnpm db:migrate
39+
pnpm db:seed
4040
```
4141

42-
3. Create `.env` for your environment
42+
> ⚠️ **WARNING**: `pnpm db:seed` is destructive — it truncates all tables before inserting fixture data. Do not run it against a database you need to preserve.
43+
44+
5. Start the development server.
4345

4446
```bash
45-
echo DATABASE_URL="postgresql://postgres:password@localhost:5433/locations" > .env
47+
pnpm dev
4648
```
4749

48-
The above `DATABASE_URL` is for the [Tanzania-locations-database](https://github.com/HackEAC/tanzania-locations-db) Docker container provision.
50+
## Useful Scripts
4951

50-
4. Sync up your API with the locations database:
52+
```bash
53+
pnpm db:migrate
54+
pnpm db:seed
55+
pnpm lint
56+
pnpm typecheck
57+
pnpm build
58+
pnpm test
59+
pnpm test:ci
60+
pnpm openapi:json
61+
```
5162

52-
- **a.** Pull existing DB schema into your Prisma schema
63+
## Migration Behavior
5364

54-
```bash
55-
pnpx prisma db pull
56-
```
65+
- `pnpm db:migrate` is the supported entrypoint for schema changes in this repo
66+
- On a fresh database it bootstraps the historical `init` migration, marks that baseline as applied, and then deploys later migrations
67+
- On an existing database that already has the older Prisma migration history, it only applies the new additive migrations
68+
- Prefer `pnpm db:migrate` over calling `prisma migrate deploy` directly
5769

58-
- **b.** Create migration init files
70+
## Testing
5971

60-
```bash
61-
mkdir prisma/migrations/init
62-
```
72+
- `pnpm test` expects a database that has already been migrated and seeded
73+
- `pnpm test:ci` runs `generate`, `db:migrate`, `db:seed`, and the Jest suite in one command
74+
- For a clean local verification flow, run:
6375

64-
- **c.** Mark the current schema as baseline
76+
```bash
77+
pnpm db:migrate
78+
pnpm db:seed
79+
pnpm test
80+
```
6581

66-
```bash
67-
pnpx prisma migrate diff \
68-
--from-empty \
69-
--to-schema-datamodel prisma/schema.prisma \
70-
--script > prisma/migrations/init/migration.sql
71-
```
82+
## API Base Paths
7283

73-
- **d.** Create migration history manually
84+
- `/v1`: canonical path for current integrations
85+
- `/api`: compatibility alias for older consumers
7486

75-
```bash
76-
pnpx prisma migrate resolve --applied init
77-
```
87+
Both base paths return the same payload shapes.
7888

79-
✅ Now you're synced! Future `prisma migrate dev` or `migrate deploy` will work cleanly.
89+
## Main Endpoints
8090

81-
5. Start development server
91+
### Collections
8292

83-
```bash
84-
npm run dev
85-
```
93+
- `GET /v1/countries`
94+
- `GET /v1/regions`
95+
- `GET /v1/districts`
96+
- `GET /v1/wards`
97+
- `GET /v1/places`
8698

87-
6. Build application
99+
### Detail Routes
88100

89-
```bash
90-
npm run build
91-
```
101+
- `GET /v1/countries/:id`
102+
- `GET /v1/regions/:regionCode`
103+
- `GET /v1/districts/:districtCode`
104+
- `GET /v1/wards/:wardCode`
105+
- `GET /v1/places/:id`
92106

93-
7. Start production server
107+
### Nested Routes
94108

95-
```bash
96-
npm run start
97-
```
109+
- `GET /v1/countries/:countryCode/regions`
110+
- `GET /v1/regions/:regionCode/districts`
111+
- `GET /v1/districts/:districtCode/wards`
112+
- `GET /v1/wards/:wardCode/places`
98113

99-
## API Endpoints
114+
### Search
100115

101-
### Countries
102-
- `GET /v1/countries` - Get all countries
103-
- `GET /v1/countries/:id` - Get country by ID
116+
- `GET /v1/search?q=nzuguni`
104117

105-
### Regions
106-
- `GET /v1/regions` - Get all regions
107-
- `GET /v1/regions/:regionCode` - Get region by code
108-
- `GET /v1/regions/:regionCode/districts` - Get districts in a region
118+
## Collection Query Parameters
109119

110-
### Districts
111-
- `GET /v1/districts` - Get all districts
112-
- `GET /v1/districts/:districtCode` - Get district by code
113-
- `GET /v1/districts/:districtCode/wards` - Get wards in a district
120+
All collection endpoints support:
114121

115-
### Wards
116-
- `GET /v1/wards` - Get all wards
117-
- `GET /v1/wards/:wardCode` - Get ward by code
118-
- `GET /v1/wards/:wardCode/places` - Get places in a ward
122+
- `page`
123+
- `limit`
124+
- `search`
119125

120-
### Places
121-
- `GET /v1/places` - Get all places
122-
- `GET /v1/places/:id` - Get place by ID
126+
Additional filters:
123127

124-
### Search
125-
- `GET /v1/search?q=nzuguni` - Fulltext search for locations by name
128+
- `/regions`: `countryId`
129+
- `/districts`: `countryId`, `regionCode`
130+
- `/wards`: `countryId`, `regionCode`, `districtCode`
131+
- `/places`: `countryId`, `regionCode`, `districtCode`, `wardCode`
126132

127-
## Running Tests
133+
## Docs
128134

129-
```bash
130-
npm test
131-
```
135+
- Swagger UI: `http://localhost:8080/api-docs`
136+
- OpenAPI JSON: `http://localhost:8080/openapi.json`
137+
- `pnpm openapi:json` exports the spec to `generated/openapi/openapi.json`
138+
139+
## Database Notes
140+
141+
- Prisma configuration lives in [prisma.config.ts](./prisma.config.ts)
142+
- The checked-in migration chain now creates the `general.search_vector` column, trigger, and GIN index used by `/search`
143+
- Seed data is intentionally small and deterministic so CI and tests can assert exact results
144+
- The seed is destructive by design for local/CI fixture setup; do not run it against a database you expect to preserve unchanged
145+
146+
## Dependency Automation
147+
148+
- `.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`
132150

133151
## License
134152

135-
This project is licensed under the CopyLeft License – see the LICENSE file for details.
153+
This project is licensed under the CopyLeft License. See [LICENSE](./LICENSE).

0 commit comments

Comments
 (0)