Find a file
2026-11-04 22:42:07 -06:00
.claude/skills docs: stop naming sibling sites by name in public docs 2026-10-14 15:55:03 +00:00
.github/workflows fix: bump checkout and setup-node actions to v5 (Node 24) 2026-10-10 00:25:47 -05:00
.vscode chore: suppress CSS lint warnings for Tailwind v4 at-rules 2026-05-31 23:32:48 -05:00
context feature: link content license (CC BY 4.0) from site footer 2026-11-03 13:57:13 -06:00
docs/screenshots docs: update saint page screenshot with formatted beatification date 2026-11-03 14:05:00 -06:00
drizzle feat: add Spanish translation infra, live on saint detail page (Phase A) 2026-10-19 18:50:15 +00:00
public Add dark-mode favicon 2026-11-05 04:39:49 +00:00
scripts feat: add Spanish translation infra, live on saint detail page (Phase A) 2026-10-19 18:50:15 +00:00
src Add dark-mode favicon 2026-11-05 04:39:49 +00:00
tests feat: add Holy Family, Baptism of the Lord, Christ the King to feast calendar 2026-10-21 19:46:11 +00:00
.env.example chore: confirm target before db:migrate, add .nvmrc and engines, fix docs drift (backlog #31, #41) 2026-10-09 18:27:22 -05:00
.gitignore fix: resolve cloudflare:workers type declaration error 2026-07-17 10:54:32 -05:00
.nvmrc chore: confirm target before db:migrate, add .nvmrc and engines, fix docs drift (backlog #31, #41) 2026-10-09 18:27:22 -05:00
astro.config.mjs feat: redesign UI with luxury editorial aesthetic and dark mode 2026-05-31 23:01:29 -05:00
CLAUDE.md docs: stop naming sibling sites by name in public docs 2026-10-14 15:55:03 +00:00
drizzle.config.ts feat: initialize project and database layer 2026-05-29 22:27:00 -05:00
eslint.config.js fix: clear no-explicit-any warnings and make the rule an error (follow-up to backlog #28) 2026-10-09 18:19:51 -05:00
LICENSE docs: rewrite README for portfolio and add MIT license 2026-11-03 12:34:21 -06:00
package-lock.json fix: upgrade vitest 3.2.7 -> 4.1.12 to resolve last 2 Dependabot alerts (#66) 2026-11-02 08:54:18 -06:00
package.json docs: standardize README structure and add license field 2026-11-03 20:38:27 +00:00
README.md docs: standardize README structure and add license field 2026-11-03 20:38:27 +00:00
tsconfig.json fix: resolve cloudflare:workers type declaration error 2026-07-17 10:54:32 -05:00
vitest.config.ts feat: isolate API tests on in-process PGlite database 2026-09-30 22:40:36 -05:00
wrangler.jsonc Rename Cloudflare Worker to themiracleregister 2026-11-04 19:00:47 +00:00

The Miracle Register

CI License: MIT Content: CC BY 4.0

A searchable database of miracles attributed to Catholic saints, with a public REST API.

Live site: themiracleregister.org | API docs: themiracleregister.org/api

The Miracle Register home page with the featured saints carousel
More screenshots Saint page for Fulton Sheen with biography, miracle, locations map and sources World map of saint locations and miracle sites with filters

No existing site exposes saint intercession miracles as structured, queryable data. Miracle Hunter and similar sites collect case write-ups, but none lets you filter by saint, diagnosis, approval authority, country or time period, or build on the data through an API.

Each miracle is a record, not an article. A healing links to the saint's canonization case, the recipient's diagnosis and cure characteristics, the medical board verdict that examined it, the decree or other authority that recognized it, and the sources behind the write-up. That structure lets you ask "show me every Vatican-approved miracle used for a canonization" and get an answer.

Currently 30 saints and 85 records are published (68 miracles and 17 apparitions).

Features

  • Browse and filter saints and miracles by type, topic, theme, country, patronage and canonization stage
  • Full-text search with phrase, OR and exclusion operators, stemmed and accent-insensitive
  • Feast day calendar, including movable feasts resolved against the Easter date
  • Map of shrines, tombs and birthplaces, and a miracle timeline
  • Source trail on every record, with a documented verification methodology
  • Read-only REST API with a generated OpenAPI spec and an interactive reference
  • Machine-translated Spanish saint pages

Tech Stack

Layer Choice
Language TypeScript, full stack
Frontend Astro (SSR) with the Cloudflare adapter
API Hono at /api/v1/*, same Worker as the site
Hosting Cloudflare Workers with static assets
Database Neon serverless Postgres
ORM Drizzle
Validation, types, OpenAPI Zod via @hono/zod-openapi
Tests Vitest on in-process PGlite databases
CI/CD GitHub Actions check, deploys through Cloudflare Workers Builds

Architecture

flowchart LR
    R["Content repo<br/>one JSON file per record"] -->|"import:content<br/>dry run, then --apply"| DB[(Neon Postgres)]
    DB --> W
    subgraph W["Cloudflare Worker"]
        A["Astro SSR pages"]
        H["Hono API /api/v1/*"]
    end
    Z["Zod schemas"] -.-> A
    Z -.-> H
    W --> U["Browsers and API clients"]

Zod schemas are shared by the pages and the API and generate the OpenAPI spec, so the spec cannot drift from the implementation.

Engineering Highlights

  • Files as the source of truth. Content lives as one JSON file per saint and miracle in a separate repository and goes through pull request review. npm run import:content upserts the files in a single transaction. It is a dry run by default and prints a field-level diff. It never publishes, never deletes, and refuses changes to published rows unless asked. The same data-integrity rules that npm run check:data runs also run inside the import transaction, and new errors roll it back.
  • Deliberate retirement path. Taking a record down is a separate script, retire:content, with its own dry run and safeguards, so an import can never remove content by omission.
  • Postgres full-text search. Stored generated tsvector columns with GIN indexes, an unaccent wrapper so the columns can be indexed, weighted fields and websearch_to_tsquery. Results are ranked, and the page and the true total come from one statement.
  • Tests that never touch production. Each test file builds its own PGlite database from the real drizzle/ migrations, so CI needs no secrets and fork pull requests pass. There are 40 test files covering the API, importer, search, privacy rules and data checks.
  • Privacy by construction. Recipients can be marked confidential or first-name-only. Redaction is applied in the API layer and checked by tests and by a check:data rule that looks for restricted names in free text.
  • Data integrity checks. check:data enforces slug format, controlled vocabularies, source coverage, mirrored relations, feast field validity and more, and exits non-zero on errors.
  • Guarded production operations. There is a single production database branch, so db:migrate and import:content --apply print the target and require confirmation. Non-interactive runs refuse without --yes.
  • Operational details. Slug renames are recorded by database triggers and served as 301 redirects. Rate limiting uses Workers bindings and fails open. Cache policies are set per page type. Wikimedia images are rewritten to sized thumbnails. Structured data (JSON-LD), canonical URLs and a sitemap are generated, and accessibility and color contrast are tested.

API

Free, read-only and keyless. Interactive reference: themiracleregister.org/api/reference.

curl "https://themiracleregister.org/api/v1/miracles?approval_authority=vatican_dicastery&used_for_canonization=1&limit=1"
{
  "data": [
    {
      "id": 714,
      "slug": "healing-of-maria-pellemans",
      "title": "Healing of Maria Pellemans",
      "type": "healing",
      "country": "France",
      "date_of_event": "1923-03-01",
      "date_precision": "month",
      "was_medically_verified": true,
      "approval_authority": "vatican_dicastery",
      "cure_details": "Sudden and complete healing while kneeling at Therese's tomb in Lisieux ...",
      "used_for_beatification": false,
      "used_for_canonization": true,
      "saints": [{ "id": 317, "slug": "therese-of-lisieux", "name": "Thérèse of Lisieux" }]
    }
  ],
  "meta": { "page": 1, "limit": 1, "total": 27 },
  "error": null
}

(Trimmed: topics, recipient_name and the full cure_details are omitted.)

Method Path Description
GET /api/v1/saints List saints (canonization_stage, theme, religious_order, nationality, page, limit)
GET /api/v1/saints/:slug One saint with related saints and linked miracles
GET /api/v1/miracles Search and filter (saint_id, type, topic, category, country, year_from, year_to, used_for_beatification, used_for_canonization, approval_authority, page, limit)
GET /api/v1/miracles/:slug One miracle with full details, sources and images
GET /api/v1/search Full-text search over saints and miracles (q, topic, page, limit); q supports "phrases", OR and -exclusions
GET /api/v1/types Miracle types
GET /api/v1/metadata Canonical filter options
GET /api/v1/doc OpenAPI 3 spec

Responses use a { data, meta, error } envelope. The API is rate limited to 60 requests per minute per client.

Local Development

Requires Node 22.13 or later (.nvmrc pins the version CI uses).

npm install
cp .env.example .env     # set DATABASE_URL
npm run types            # generate Worker types
npm run dev
npm test                 # unit and integration tests, no database needed
npm run typecheck
npm run lint
npm run build

npm run dev reads from the database in DATABASE_URL. The tests do not use it. The site's content is not included in this repository, so to run the site locally you need your own Postgres database with the schema applied (npm run db:migrate) and your own content files for npm run import:content.

Project Status

The site, API, search, calendar, map and importer are live. Not built yet: end-to-end tests (Playwright), a Prettier-formatted codebase and a larger corpus of published records.

Licensing

  • Code: MIT.
  • Content (saint biographies, miracle synopses and other text and data published on the site and through the API): CC BY 4.0. Attribute as "The Miracle Register, themiracleregister.org, CC BY 4.0". The research behind the content was done with AI assistance and reviewed by the author; the Spanish pages are machine translated. All content is provided without warranty.
  • Images are hosted by Wikimedia Commons and keep their own licenses. They are not covered by the CC BY grant above.