Skip to content
GitHub Twitter

Rebuilding Quizolai

Quizolai started as a small NestJS + Angular project — there wasn't a decent open API for German quiz questions, so I made one. It picked up around 50 users and modest Google rankings ("quizfragen api", "quizfragen datenbank") over a couple of years.

By the time I wanted to add a paid tier, the codebase had drifted into the awkward middle between side project and real product. The parts I touched most often — auth, billing, persistence — were the parts I was least happy with. So I rewrote it.

The working repo name is requizolai; it's the same product.

Stack

Backend: Spring Boot 3 + Kotlin on Java 21. PostgreSQL via JPA, schema versioned with Liquibase. The NestJS service had accumulated hand-rolled DI, validation and error mapping that Spring gives you for free.

Frontend: Angular 17 with Material + Tailwind. Same framework as v1 — kept one piece familiar while everything else changed.

Identity: Auth0 (magic link, Google, GitHub) instead of the hand-rolled magic-link flow.

Payments: Stripe.

Other pieces: Caffeine for in-memory caching, Spring AI → OpenAI for the question of the day, Brevo for transactional email, springdoc-openapi to publish the spec the docs site consumes.

For tests: Testcontainers + JUnit 5 against a real PostgreSQL, MockK for unit tests, and ArchUnit to enforce module boundaries.

Auth

v1 issued its own JWTs after a magic-link bounce. It worked, but I owned a lot of code I didn't want to own — token signing, refresh, the password-less flow.

v2 delegates entirely to Auth0. The backend is an OAuth2 resource server: it validates Auth0-issued JWTs against the published JWKS and trusts the claims. Adding Google and GitHub login was a checkbox in the Auth0 dashboard. The trade is a hard dependency on Auth0 — worth it.

Login

Stripe

The Pro tier is the main new thing. Flow:

  1. User picks a plan.
  2. Backend creates a Stripe Checkout Session and returns the URL.
  3. User completes payment on Stripe's hosted checkout.
  4. Stripe fires webhooks back (checkout.session.completed, customer.subscription.*).
  5. The webhook handler updates entitlements and invalidates the relevant Caffeine caches so the next API call sees the new plan immediately.

Stripe's Customer Portal handles cancellations, payment method changes and invoices, so the app doesn't need its own billing UI.

Pricing

Notable features

Collections. A curated group of questions you can build and share. The category/difficulty taxonomy is good for discovery; collections are good for specific use-cases ("a quiz for trivia night").

Collections

Question editing. Unverified → verified pipeline driven by community votes. Multi-type (single choice, multiple choice, free-text), markdown content, image upload, per-question history.

Add question

Question of the day. Spring AI picks one from the verified set and renders it on the dashboard.

Dashboard. Per-user verified/unverified counts, points, leaderboard, recent collections.

Dashboard

What's next

The rewrite isn't deployed yet. Remaining: the v1 → v2 data migration, the deploy pipeline, and a final pass on the OpenAPI spec so docs.quizolai.de can switch over.