MAL
|
|
MAL For everyone who wanted MyAnimeList-style tracking, but with cleaner UI, better UX, and fewer compromises. |
A calmer anime tracker built for people who care about flow.
Discover anime, track progress, follow sequels, and keep your watchlist clean without fighting noisy UI.
Why this project exists
I built this because I genuinely disliked the UI and UX of the alternatives.
Most anime tracking tools I tried felt cluttered, slow to navigate, or missing basic pieces I wanted in one place. I wanted a product that felt focused: search quickly, open details quickly, decide status quickly, and move on.
This project is that answer.
Under the hood, it is also an engineering exercise in building a reliable product on top of an unreliable upstream data source. Anime metadata APIs can rate-limit, timeout, or intermittently fail. Instead of pretending that never happens, this codebase is designed around that reality.
What you can do with it
- Browse a catalog view of popular anime
- Discover airing and upcoming shows
- Search instantly from the header quick-search
- Open anime detail pages with related titles and recommendations
- Add, update, remove, import, and export watchlist entries
- Track statuses (
watching,completed,plan_to_watch, etc.) - Get notification-oriented views for tracking and upcoming sequels
- Register/login/logout, rotate account recovery keys, and recover accounts
Product philosophy
- Minimal friction: fewer clicks and less visual noise
- Practical over perfect: fast, readable pages over heavy front-end complexity
- Resilient by default: graceful fallback behavior when upstream services fail
- Honest constraints: explicitly acknowledge tradeoffs and incomplete pieces
Technical overview
This is a server-rendered Go web application with SQLite persistence, generated SQL accessors, background workers, and templ-based UI rendering.
Stack
- Go
1.24 - SQLite (
github.com/mattn/go-sqlite3) sqlcfor typed query generationtemplfor server-side HTML components- HTMX + small vanilla JS modules for interactivity
- Jikan API (
https://api.jikan.moe/v4) as primary anime data provider goqueryfor watch-order parsing/fallback extraction
Repository layout
cmd/server/ # app entrypoint
internal/server/ # route composition + middleware wiring
internal/features/anime/ # anime handlers + service logic
internal/features/watchlist/ # watchlist handlers + service logic
internal/features/auth/ # auth/session/recovery logic
internal/jikan/ # upstream API client + caching + retry paths
internal/worker/ # relation sync + retry processing + cache cleanup
internal/database/ # sqlc models/queries + migration runner
internal/templates/ # templ views and partials
migrations/ # schema evolution scripts
static/ # CSS/JS assets
How the app works
At startup, the server:
- Opens SQLite (
DATABASE_FILE, defaultmal.db) - Runs migrations
- Initializes typed DB queries, auth service, and Jikan client
- Starts a background worker
- Starts HTTP server on
PORT(default3000)
Request flow is intentionally straightforward:
- Request enters
http.ServeMux - Middleware enforces origin checks and auth boundaries
- Feature handlers call feature services
- Services read/write SQLite and/or fetch from Jikan
- Templ renders pages or partials
The hard parts (and how they are handled)
1) Upstream instability
Jikan can return 429/5xx, intermittently timeout, or vary response behavior under load.
Mitigations implemented in this codebase:
- Request pacing to stay under known rate limits (base delay between requests)
- Retry with bounded backoff and
Retry-Aftersupport - Cache-first reads where possible
- Stale cache fallback if fresh fetch fails
- Persisted retry queue (
anime_fetch_retry) for retryable failures - Worker signaling so retries can be processed quickly after enqueue
2) Keeping sequel graphs useful
Finding upcoming sequels is not just a single lookup. The app keeps relation data updated using worker jobs and recursive SQL CTEs, then maps those relations back to your watchlist context.
3) UI responsiveness without a heavy front-end framework
The app uses server-rendered templates plus HTMX partial updates and focused JS files. The goal is immediate UI feedback with less complexity than a large SPA stack.
4) Security basics done properly
- Password rules enforce complexity and minimum length
- Password hashes use bcrypt
- Session cookies are
HttpOnlyandSameSite=Strict Securecookies are enabled in production mode (ENV=production)- Origin/Referer checks protect non-GET actions
- Auth routes are rate-limited per IP
Getting started
Prerequisites
- Go
1.24+ - SQLite
templCLI
Local development
# install templ CLI
go install github.com/a-h/templ/cmd/templ@latest
# generate templ code
templ generate
# run server
go run ./cmd/server
Open http://localhost:3000.
Docker
The repository includes a multi-stage Dockerfile that:
- installs dependencies
- generates templ files
- builds
./cmd/server - ships a slim runtime image with SQLite support
Example:
docker build -t mal .
docker run --rm -p 3000:3000 mal
Configuration
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP listen port |
DATABASE_FILE |
mal.db |
SQLite database file path |
ENV |
(empty) | Set to production to mark session cookies as secure |
Database and migrations
Migrations run automatically on startup.
Current migration history covers:
- initial auth/session/watchlist schema
- anime title and airing metadata
- notifications data model
- relation graph support
- Jikan response caching
- query-performance indexes
- account recovery key support
- persisted anime fetch retry queue
Testing
The repo includes tests around core behavior such as:
- watchlist service logic
- watch-order parsing behavior
- relation helpers
- auth middleware behavior
Run all tests:
go test ./...
Tradeoffs and known limitations
- Watchlist sorting by
scoreis currently a placeholder path - External data quality and uptime still depend on Jikan/third-party sources
- There is no formal CI pipeline configured in this repository yet
- Project docs (contributing/license) are still lightweight and evolving
Roadmap direction
- Complete score sorting semantics for watchlist
- Expand test coverage for handler + integration paths
- Add clearer contribution and governance docs
- Improve observability around worker retries and cache health
- Continue refining the UI for speed and clarity over visual noise
Development notes
- Generated templ outputs (
*_templ.go) are checked in - SQL is authored in
internal/database/queries.sqland generated throughsqlc - Static assets live in
static/
Contributing
Please read CONTRIBUTING.md before opening a pull request.
If you want to contribute, open an issue or pull request with:
- the user-facing problem you are solving
- the technical approach and tradeoffs
- before/after behavior notes
Small, focused changes are preferred over broad rewrites.
Security and secrets
- Do not commit real API keys or private credentials
- Keep local
.envvalues out of documentation and screenshots - If you discover a security issue, report it privately before public disclosure
License
MIT. See LICENSE.
If this project resonates with you, it is probably for the same reason it exists: you want anime tracking that gets out of your way and lets you focus on actually watching.