The project
An AI-assisted Magic: The Gathering card discovery and deckbuilding app. A mono-repo of four services: a FastAPI backend, a query microservice that turns natural language into an intermediate representation and then into Scryfall queries, a Redis-cached card image proxy, and a Next.js frontend, with Prometheus and Grafana wired in from the start.
How it fits together
- A query service that goes natural language, then intermediate representation, then Scryfall query, with Redis caching between stages.
- A dedicated card image proxy with its own cache and metrics, keeping image traffic off the main backend.
- A Next.js 15 frontend in TypeScript with Tailwind and Mantine.
- Prometheus metrics and Grafana dashboards alongside a Docker Compose stack wiring Postgres, Redis and every service together for local development.
The trade-offs that mattered
An intermediate representation between the model and the API
Asking a language model to emit Scryfall syntax directly is brittle and hard to debug. Going via a structured IR makes the model's output inspectable and lets the query layer be tested without the model at all.
Cache aggressively at every boundary
Card data barely changes and model calls cost money and latency. Redis between each stage made the app feel instant on repeat queries.
Observability from day one on a side project
Wiring Prometheus and Grafana in early cost an afternoon and paid for itself the first time a service started misbehaving under load.
What I took from it
Translating natural language into a structured intermediate representation before hitting the search API, rather than asking a model for the answer directly, is what made the results trustworthy.
- Putting a structured representation between a language model and a downstream API turns an unpredictable component into a testable one.
- Splitting services out is worth it when they have genuinely different caching and scaling needs, and not much before that.