mkelvers a9dfb77bc4 fix: standardize command palette styles
Add ring, border separator, font-normal, and focus-visible styles to search and command palette.
2026-05-25 01:55:28 +02:00
2026-05-24 22:46:21 +02:00
2026-05-25 01:15:39 +02:00
2026-05-18 17:49:03 +02:00
2026-05-12 10:07:05 +02:00
2026-05-24 22:47:52 +02:00
2026-05-24 22:36:41 +02:00
2026-05-14 12:42:17 +02:00
2026-05-14 12:42:17 +02:00
2026-05-24 22:45:51 +02:00
2026-04-14 22:36:32 +02:00

MyAnimeList

MyAnimeList logo MyAnimeList
My personal anime tracker, built because nothing else felt right.

Go SQLite Tailwind HTMX


Why this project exists

I built this for myself.

I was frustrated with the UI and UX of every tracker I tried. Even when something looked decent, it still felt awkward to use day-to-day, or it was missing pieces I considered essential. I wanted one place that matched how I actually watch anime: search fast, get context fast, update status fast, and move on.

So this project is personal first and public second. I put it on GitHub because I like shipping in the open, not because it was originally designed as a general-purpose product for everyone.

Technically, I also wanted to prove that a small, server-rendered Go app could stay reliable even when upstream anime APIs are inconsistent. A lot of this code exists because real APIs rate-limit, timeout, and occasionally fail at the worst possible moment.

What the application offers

For my own workflow, MyAnimeList combines catalog browsing, seasonal discovery, quick search, detail pages with recommendations and relations, watchlist management, continue-watching, and in-app playback in one server-rendered interface.

The interface is minimal and functional, featuring a dark theme and quick access to tracking tools.

Technical approach

The application is written in Go and rendered on the server with html/template, with SQLite as the primary datastore and sqlc for typed query generation. Styling uses Tailwind CSS v4. HTMX and small TypeScript modules handle incremental interactions, which keeps the interface responsive without moving the entire product into a heavy client-side architecture.

The external anime data source is Jikan (https://api.jikan.moe/v4). Because reliability is a first-class concern, the client layer includes request pacing, bounded retries, backoff behavior, stale-cache fallback, and a persisted retry queue for failed fetches. Playback proxying uses uTLS to bypass Cloudflare protections.

Upstream APIs can fail transiently with 429 and 5xx responses, so the app favors graceful degradation over hard failure. Cached values are used when fresh requests fail, retryable failures are persisted and replayed in a background worker, and relation synchronization is incremental so one bad fetch does not block the rest of the graph.

Repository structure

The codebase follows standard Go project layout conventions.

Path Purpose
api/* Feature routes: anime, auth, playback, watchlist
cmd/server Application entrypoint and CLI commands
integrations/* External API clients and scraping
internal/* Core services: db, middleware, server, worker
pkg/middleware Generic HTTP middleware
templates/* Server-rendered HTML templates
migrations Schema evolution
static / dist Frontend assets

Getting started

Requires Go 1.25+, Bun, and just (brew install just).

git clone https://github.com/mkelvers/mal.git && cd mal
openssl rand -base32 32
PLAYBACK_PROXY_SECRET="your-32-char-secret" go run ./cmd/server
go run ./cmd/user <username> <password>

The app runs at http://localhost:3000.

Tasks

The justfile automates common tasks:

just fmt          # format go code
just lint         # go fmt && go vet
just test         # run go tests
just build        # build go binary + frontend
just check        # lint, test, typecheck, build
just dev          # build and run
just install-hooks   # install pre-push hooks

Docker

docker build -t mal .
docker run --rm -p 3000:3000 -e PLAYBACK_PROXY_SECRET="$(openssl rand -base32 32)" mal

# persistent data
docker run --rm -p 3000:3000 \
  -e DATABASE_FILE=/app/data/mal.db \
  -e PLAYBACK_PROXY_SECRET="your-secret" \
  -v "$(pwd)/data:/app/data" \
  mal

docker exec mal ./cmd/user <username> <password>

Configuration

Variable Default Description
PORT 3000 HTTP listen port
DATABASE_FILE mal.db SQLite database file path
ENV (empty) Set to production to enable secure session cookies
MIGRATIONS_DIR (auto-discovered) Optional explicit path to migration files
PLAYBACK_PROXY_SECRET (required) HMAC secret for signed playback proxy tokens (min 32 chars)
MAL_JIKAN_TRACE false Log all Jikan cache/upstream timings when enabled

Testing

Run locally with just check or manually:

go test ./...

Migrations run automatically on startup.

Security

Keep secrets out of version control, do not publish real credentials in documentation or screenshots, and report security issues privately before public disclosure.

License

This project is released under the MIT License. See LICENSE for details.

Description
No description provided
Readme MIT 32 MiB
Languages
Go 72.3%
TypeScript 26.9%
CSS 0.3%
Dockerfile 0.2%
Just 0.2%