KATHER is an Express.js and Prisma service for exploring a curated Arabic poetry dataset. It exposes poets, poems, individual lines, catalog metadata, health checks, readiness checks, Prometheus metrics, and OpenAPI documentation.
The API is designed to keep Arabic text UTF-8 safe, preserve right-to-left display at the client layer, and support fast discovery through PostgreSQL trigram indexes.
- REST API for poets, poems, lines, and catalog filters
- PostgreSQL-backed persistence via Prisma
- Redis-backed count caching and rate-limit storage
- Health and readiness endpoints for deployment checks
- OpenAPI 3.1 specification plus Swagger UI and ReDoc
- Trigram indexes for substring search across Arabic and English fields
- Node.js 22 or newer
- PostgreSQL 15+ with the
pg_trgmextension available - Redis 6+ if you want caching and rate limiting to work normally
npm install
cp .env.example .env
npm run devThe application starts on http://localhost:3000 by default.
The project reads configuration from .env.
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string used by Prisma |
PORT |
No | HTTP port for the Express server |
NODE_ENV |
No | Controls logging and error verbosity |
TRUST_PROXY |
No | Express trust proxy setting for deployments behind a reverse proxy |
CORS_ORIGIN |
No | Comma-separated allowlist for browser origins |
REDIS_HOST |
No | Redis host for cache and rate-limit storage |
REDIS_PORT |
No | Redis port |
REDIS_PASSWORD |
No | Redis password, if required |
METRICS_BASIC_AUTH_USER |
No | Basic auth username for /metrics |
METRICS_BASIC_AUTH_PASSWORD |
No | Basic auth password for /metrics |
Notes:
DATABASE_URLis required at startup.- Redis is used for caching counts and backing the rate-limit store. If Redis is unavailable, the API can still respond, but cache behavior and readiness checks will degrade.
- Set both metrics auth variables in production. If both are empty,
/metricsis currently left open by the service.
- Create the PostgreSQL database referenced by
DATABASE_URL. - Ensure the
pg_trgmextension is available. - Apply migrations.
- Generate the Prisma client.
- Seed the dataset if you want the API populated locally.
Typical local workflow:
npx prisma migrate dev
npx prisma generate
npm run seedThe repository also includes prisma/add_trgm_indexes.sql for environments where you want to create trigram indexes manually with psql.
The seed pipeline loads JSON data from seedData/ and inserts poets, poems, and poem lines in that order.
npm run seedSeeding details:
- Poet IDs and poem IDs are explicitly preserved from the seed files.
- Poem lines are inserted in batches for performance.
- Duplicate inserts are skipped, which makes the seed idempotent but can also hide partial dataset issues if the source JSON is inconsistent.
Development:
npm run devProduction:
npm startDocker:
docker build -t KATHER .
docker run --rm -p 3000:3000 --env-file .env KATHERThe service exposes both canonical and versioned aliases. In practice, you can use either the root routes or the /v1 routes depending on your client preference.
Common route groups:
/healthand/ready/metrics/catalogand/v1/catalog/poetsand/v1/poets/poemsand/v1/poems/linesand/v1/lines
Documentation routes:
/openapi.yaml/openapi.json/docs/redoc
The public endpoints use middleware-level validation for:
- integer IDs
- limit and offset bounds
- catalog filters such as era, country, sea, quafia, and gender
- search query length
- poem and line type constraints
This keeps invalid input out of the Prisma layer and reduces the chance of runtime query errors.
The Prisma schema models three main entities:
PoetsPoemsPoemsLines
Important implementation details:
- Arabic and English fields are stored as plain PostgreSQL text columns, which is UTF-8 safe.
- Search is implemented with
containsfilters and supported by trigram indexes. - The schema uses composite indexes for common ordering patterns such as
poetId + order. - The data layer prefers deterministic ordering so pagination remains stable across requests.
Arabic content is preserved as UTF-8 throughout the stack.
The current search strategy is pragmatic rather than linguistic:
- it uses trigram indexes for fast partial matching
- it does not perform full Arabic stem analysis or general-purpose text normalization
- line search falls back to a diacritic-stripped content column when the query has no diacritics
- results are best for discovery, browsing, and fuzzy search, not for strict lexical search
If you need true Arabic NLP search later, that will require a dedicated normalization or full-text pipeline.
- Count queries are cached in Redis.
- Rate limiting uses a Redis-backed store when Redis is available.
/metricsexposes Prometheus metrics./readychecks both PostgreSQL and Redis.
Before publishing or deploying publicly:
- Never commit a real
.envfile. - Keep
DATABASE_URLand Redis credentials in secrets management, not in source control. - Set
METRICS_BASIC_AUTH_USERandMETRICS_BASIC_AUTH_PASSWORDin production. - Review
CORS_ORIGINbefore deployment and avoid wildcard CORS unless you intentionally want public browser access. - Treat readiness endpoint error details as operational data; do not expose them more widely than necessary.
src/
controllers/ Request handlers and response shaping
database/ Prisma client setup
middleware/ Validation, auth, pagination, and request context helpers
routes/ Express route wiring
services/ Data access and domain logic
utils/ Shared helpers, cache, and error types
prisma/
schema.prisma Prisma schema
seed/ Seed loaders
migrations/ Database migration history
seedData/ JSON source data for poets, poems, and lines
npm run dev
npm start
npm run seed
npm run validate:docsBefore opening the repository publicly, verify the following:
.envis excluded from Git history and not present in commits.env.examplematches the runtime configuration you actually need- migrations apply cleanly on a fresh PostgreSQL database
- seeding works end to end against a clean database
/health,/ready,/metrics, and the documentation routes behave as expected
- Large corpus: more than 3 million lines, 2,000+ poets, and 120,000+ poems.
- Structured discovery first: the API is tuned for filtering and browsing, not ad hoc querying.
- Compact metadata layer: the catalog metadata files contain 21 countries, 11 eras, 36 quawafi, 65 seas, and 29 topics.
- Main tradeoff: line search can still be expensive on very large result sets, especially when broad filters are combined.
- Data quality reality: at this scale, some records may be misattributed or inconsistent — for example, a poem assigned to the wrong poet, lines linked to the wrong poem, or false poem type assigned. A single developer cannot manually verify a corpus this large, so if you encounter incorrect or misattributed data, please open a GitHub issue or submit a pull request so it can be corrected in the database.
- Future gap: no voice endpoint is included today; if it becomes a requirement, a cloud text-to-speech service such as Google Cloud Text-to-Speech would be a straightforward path.
ISC