Projects

03 / 05Case study

Wikiverse

Full-stack web encyclopedia

An encyclopedia built from scratch, without MediaWiki: 63 articles with revision history, word-by-word diffs, talk pages, red links and full-text search in PostgreSQL, in production on its own domain.

Entropy article with a side table of contents, a thermodynamics infobox and a red linkCoral reef article on a phone with the lead image and the infobox
My role
Product, interface, full-stack development, content and deployment
Stack
React 19 · TypeScript · Django REST Framework · PostgreSQL · Redis · Railway
Status
In production on Railway · own domain

Scope

  • 608tests in CI, from the backend to Playwright
  • 63articles written, with 428 references
  • 36API routes documented with OpenAPI

The challenge

In a wiki anyone can change the text, and readers need to trust it and find their way across many articles: traceability and navigation are the product; showing articles is the easy part.

What I built
I turned my 2024 CS50W project into a complete encyclopedia — revisions, version diffs, talk pages, PostgreSQL search, an editor with live preview and content security — took it to production with 608 tests in CI and wrote all 63 articles.
Key technical decision
Every save is an immutable revision, and the search index is maintained by a PostgreSQL trigger: neither history nor search depends on the code remembering to update them.

Design decisions

  1. Side-by-side comparison of two Marie Curie revisions with the Legacy section added
    Problem
    Text that anyone can edit loses trust if you can't see who changed what.
    Decision
    Every save is an immutable revision with author, summary and delta; any two can be diffed word by word.
  2. Coral reef revision comparison adapted to a phone screen
    Problem
    Diffing a long article word by word in a single pass took tens of seconds.
    Decision
    The diff compares lines first and only then words, inside the lines that changed.
  3. Search results for energy with highlighted snippets, a category filter and sorting
    Problem
    A search index that the code has to remember to update eventually falls out of date.
    Decision
    A PostgreSQL trigger recomputes the weighted vector of title, summary and body on every change.
  4. Wikiverse search box with suggestions for mer — Mercury the planet, the element and the disambiguation page
    Problem
    A search with a typo finds nothing, and the reader leaves.
    Decision
    Suggestions as you type and trigram similarity as a second attempt; “entrpy” finds Entropy.
  5. Preview card for the Light article over the Black hole article
    Problem
    Opening every link to see what it's about interrupts reading.
    Decision
    On hover, a card shows the linked article's short description and excerpt.
  6. Entropy article with a side table of contents, a thermodynamics infobox and a red link
    Problem
    A link to an article that doesn't exist yet usually breaks or gets hidden.
    Decision
    It shows in red, opens the editor with the title ready and turns blue once someone writes it.
  7. Entropy article editor with the fields on the left and the preview on the right
    Problem
    A preview with a different renderer ends up showing something other than what gets published.
    Decision
    The editor previews with the same Markdown pipeline as the article.
  8. Coral reef article on a phone with the lead image and the infobox
    Problem
    Contents, article and tools in three columns don't fit on a phone.
    Decision
    Below 960 px the contents move into the header and the infobox stacks; nothing overflows at 390 px.

System

Pick a module: its path lights up.Pick a module: its decision appears below.

Architecture18 modules · 17 connections

A featured article, trivia and anniversaries drawn from editable blocks; the main page is cached for a set time.

React 19 · TanStack Query 5

Below 960 px the contents move into the header and the infobox stacks; no screen overflows at 390 px.

Tailwind CSS 4.3

Markdown is rendered on the client with a custom plugin for wikilinks, footnotes and red links, with no raw HTML.

react-markdown 9 · remark-gfm 4 · Custom remark plugin

The query, filter and sort live in the URL: a search can be linked and the back button works.

React Router 7 · TanStack Query 5

Any two revisions can be compared side by side or in one column, with each change's byte delta.

React 19

Edits and talk messages in a single feed, with every filter in the URL and an RSS feed.

React Router 7

Loaded only when used, previews with the same renderer as the article and saves drafts without restoring them on its own.

React.lazy · localStorage

36 routes documented with OpenAPI; 30-minute JWT with a refresh token that rotates and is revoked on sign-out.

Django REST Framework 3.17 · SimpleJWT 5.5 · drf-spectacular 0.30

One immutable revision per save, with parent, author and delta; a save without changes creates none.

Django 5.2

websearch_to_tsquery with ranking, trigram when there are no results and snippets escaped before highlighting.

PostgreSQL full-text · pg_trgm

nginx sends only link-preview bots to Django, which answers with Open Graph and JSON-LD; people get the SPA.

Django 5.2 · Open Graph

Stats, main page, suggestions, diffs and rate limits: losing Redis costs performance, not data.

Redis · django-redis 5.4

63 articles in 21 modules validated against 31 rules; seeding twice produces exactly the same history.

Custom seed command

A trigger maintains the search vector with A, B and C weights; Postgres-only features are chosen at call time, not import time.

PostgreSQL 18 on Railway · tsvector + GIN

Frontend, backend, PostgreSQL and Redis as four services; it deploys on every push to main.

Railway · Docker

Serves the SPA, proxies /api and emits the same CSP as Django; index.html uncached and hashed assets cached forever.

nginx 1.27

608 tests, plus complete migrations, a warning-free OpenAPI schema and the corpus validated before merging.

GitHub Actions · pytest · Vitest · Playwright

Images only from Wikimedia Commons, with author and license; the allowlist is enforced on the server too.

Wikimedia Commons

Inspector18 decisions

Client

Main page

A featured article, trivia and anniversaries drawn from editable blocks; the main page is cached for a set time.

Technologies

48 tools across 10 areas. In the system, each module lists its own.

Languages

  • TypeScript 5.9
  • Python 3.13
  • SQL (PL/pgSQL)
  • CSS

Frontend

  • React 19.2
  • React Router 7.18
  • Vite 6.4
  • react-markdown 9
  • remark-gfm 4
  • Custom remark plugin

Interface

  • Tailwind CSS 4.3
  • lucide-react
  • Source Serif 4
  • Inter
  • IBM Plex Mono
  • Light and dark themes

Client state and data

  • TanStack Query 5
  • Zustand 5
  • Axios 1.18

Backend and API

  • Django 5.2
  • Django REST Framework 3.17
  • SimpleJWT 5.5
  • drf-spectacular 0.30
  • django-filter 25

Data and search

  • PostgreSQL 16 and 18
  • tsvector + GIN
  • pg_trgm
  • unaccent
  • psycopg 3
  • SQLite in development

Cache

  • Redis 7
  • django-redis 5.4

Server and deployment

  • nginx 1.27
  • Gunicorn 23
  • WhiteNoise 6.12
  • Docker Compose
  • Railway

Quality and CI

  • pytest 8.4
  • Vitest 4.1
  • Testing Library
  • Playwright 1.61
  • Ruff
  • ESLint 9
  • GitHub Actions

Tooling

  • Node.js 22
  • Makefile
  • Custom management commands
  • Git

Verifiable results

  • In production at wikiverse.jonasjavier.dev with nginx, Django, PostgreSQL and Redis on Railway
  • Immutable revisions, history and word-by-word diffs between any two versions
  • Full-text search in PostgreSQL with ranking, autocomplete and typo tolerance
  • Red links, two-way footnotes and a preview of every link on hover
  • An editor with live preview and an infobox edited row by row
  • 608 tests in CI, a strict CSP and no raw HTML anywhere in the content

Tour by module

18 screens across 5 modules.

Main page and articles4 screens

  • Entropy article with a side table of contents, a thermodynamics infobox and a red link

    Every article combines a table of contents, an infobox, citations and red links to what's missing.

  • Black hole article in the dark theme with the M87 image and its license credit

    A complete dark theme; every Wikimedia image shows its author and license.

  • Wikiverse main page with the featured article Entropy and the Did you know and On this day panels

    The main page brings together a featured article, trivia and anniversaries.

  • End of the Entropy article with numbered notes and the reference list with DOIs

    Footnotes link both ways to complete references.

History and changes2 screens

  • Side-by-side comparison of two Marie Curie revisions with the Legacy section added

    Any pair of revisions can be compared side by side, by line and by word.

  • Wikiverse recent changes with filters by type, period and contributor

    Edits and discussions in a single feed, with every filter saved in the URL.

Editing and API3 screens

  • Entropy article editor with the fields on the left and the preview on the right

    The editor previews with the same renderer as the published article.

  • Structured Entropy infobox editor with reorderable rows

    The infobox is edited as typed rows, without touching code.

  • Interactive Wikiverse API documentation generated with OpenAPI

    36 routes documented with OpenAPI and explorable from the browser.

On the phone4 screens

  • Coral reef article on a phone with the lead image and the infobox

    Articles read comfortably on the phone, with no side columns.

  • Wikiverse main page on a phone with the featured article Entropy

    On the phone, the main page opens with the featured article and search close at hand.

  • Search results for energy on a phone with highlighted snippets

    Full search works the same on the phone.

  • Coral reef revision comparison adapted to a phone screen

    The version comparison collapses to one column on the phone.

6 min read

The full case

Context

Wikiverse started in April 2024 as the "Wiki" project for CS50W, Harvard's web programming course, which I submitted and passed: Django with templates and four entries stored as Markdown files. In June 2026 I rebuilt it as a Django REST Framework API with a React app, and in September I turned it into a complete encyclopedia and took it to production.

The visual design takes Wikipedia as a reference, with its own typefaces, colors and layout. I wrote all 63 articles — about 75,000 words on science, geography and computing — and checked their 428 references.

The problem

A wiki has to solve two things at once: anyone can change the text, and the reader has to be able to trust it and find their way across many articles. That's why traceability — who changed what, and when — and navigation — links, categories and search — are the product. Showing articles is the easy part.

My role

The project is mine from start to finish: the product, the interface, the data model, the API, search, security, testing, continuous integration, deployment, documentation and content.

From CS50W to an encyclopedia

The brief asked for a page per entry, an index, substring search, creating and editing in Markdown, and a random page. Wikiverse keeps those features and adds what makes a wiki a wiki:

  • immutable revisions and word-by-word diffs between any two versions;
  • talk pages with nested replies;
  • recent changes with filters in the URL and an RSS feed;
  • a watchlist, JWT accounts and page protection;
  • red links that turn blue on their own when someone writes the article;
  • two-way footnotes and a preview of every link;
  • full-text search with autocomplete and typo tolerance;
  • a relational model in PostgreSQL instead of loose files.

Constraints

  • Two databases. Everything works on SQLite and PostgreSQL; Postgres-only features are chosen at call time, never at import time.
  • No raw HTML. No dangerouslySetInnerHTML and no HTML inside the Markdown: a test fails if it ever comes back.
  • A backend that's public too. On Railway the backend answers on its own host, outside nginx, so Django emits the same security headers.
  • Services that sleep. After a while without visits, the first request takes a few seconds.

Architecture decisions

  • Revisions. Article stores the current state, and every save creates an immutable Revision with its parent, author, summary, size and delta. A save with no changes creates no revision, and deleting keeps the history.
  • A link graph. It's rebuilt on every save. A target that doesn't exist stays as a red link with its title, and turns blue the day someone writes it.
  • A single renderer. Articles, talk pages, infoboxes, references and the editor preview all go through the same Markdown pipeline, with a custom plugin for wikilinks and notes.
  • Search. A PostgreSQL trigger maintains the weighted vector for the title, summary and body. If a query finds nothing, it's retried with trigram similarity and a title is suggested.
  • Two-stage diffs. Lines first, then words, only inside the lines that changed, with limits that truncate rather than time out.
  • Redis caching. Stats, the main page, suggestions and historical diffs, which never expire because they're immutable.

Security

  • The same CSP comes from nginx and from Django: scripts only from the site's own origin, no embedding the site, and images only from Wikimedia.
  • Search matches are marked with control delimiters, escaped, and only then turned into highlighting.
  • Nine configurable rate limits; sign-in and sign-up are also limited per user, so switching IPs doesn't reset them.
  • 30-minute JWT with a two-day refresh token that rotates and is revoked on sign-out.
  • Fonts are served from the site itself.

A SPA that shares well

A single-page app doesn't give social networks preview cards. nginx recognizes link-preview bots and sends only them to a Django view with Open Graph and JSON-LD; people and search engines get the app. The sitemap and robots.txt come from the same origin.

Reproducible data

The corpus lives in 21 modules and is validated against 31 rules before anything is written to the database. Each article's history — two to nine revisions — is derived from its title, so seeding twice produces exactly the same result. CI checks it together with the migrations and the API schema.

Challenges

  • An empty database with everything green. Production went out with no articles and the health checks didn't catch it. It was seeded, and the release process now requires checking the content, not just the health.
  • A quadratic diff. Diffing a long text word by word in a single pass took tens of seconds; the two stages solved it.
  • Five conflicting specs. They contradicted each other on 35 points, and a single decisions document became the source of truth.
  • A proxy tied to a name. The backend host was hard-coded in the nginx config; renaming the service broke the site with no error at all. It moved to environment variables.
  • A password in the history. A development admin password ended up in git; it was revoked with a migration, and the account is now created with a custom command.

What's missing

The interface is English-only. There are 57 planned articles that are red links today, lead images are served at full resolution instead of as thumbnails, the highest protection level on a new page isn't restricted to staff yet, and error monitoring is wired up but switched off. "On this day" shows eight fixed anniversaries, not the current day's.

About the content and screenshots

The article text is mine and published under CC BY 4.0; the images come from Wikimedia Commons, each with its author and license. Users, page views, talk threads and edit history are generated by the project's seed: they aren't real traffic or real contributors. The screenshots were taken on September 28, 2026, with the production stack running locally on the latest code, and the link-preview one in production. Screens with any visible defect were left out.

What I learned

A green health check doesn't prove the product works: you have to check the content. Pinning down a written contract before building avoided reopening decisions. And security that tests itself — a test that fails if raw HTML comes back — holds up better than security reviewed by hand.

Current status

In production at wikiverse.jonasjavier.dev: nginx and Django on Railway, with PostgreSQL and Redis. If it hasn't had visits for a while, the first load can take a few seconds. The repository is public under the MIT license, and continuous integration passes 608 tests: 333 backend, 258 Vitest and 17 Playwright journeys.