Files
mal/README.md

8.3 KiB

MAL

MAL logo 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.

Go SQLite templ HTMX


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)
  • sqlc for typed query generation
  • templ for server-side HTML components
  • HTMX + small vanilla JS modules for interactivity
  • Jikan API (https://api.jikan.moe/v4) as primary anime data provider
  • goquery for 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:

  1. Opens SQLite (DATABASE_FILE, default mal.db)
  2. Runs migrations
  3. Initializes typed DB queries, auth service, and Jikan client
  4. Starts a background worker
  5. Starts HTTP server on PORT (default 3000)

Request flow is intentionally straightforward:

  1. Request enters http.ServeMux
  2. Middleware enforces origin checks and auth boundaries
  3. Feature handlers call feature services
  4. Services read/write SQLite and/or fetch from Jikan
  5. 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-After support
  • 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 HttpOnly and SameSite=Strict
  • Secure cookies 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
  • templ CLI

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 score is 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.sql and generated through sqlc
  • 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 .env values 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.