# MyAnimeList

MyAnimeList logo

A local-first anime catalog, watchlist, recommendation, and playback app.

Go SQLite Bun Tailwind HTMX License

--- MyAnimeList is a self-hosted media app for browsing anime, managing a watchlist, resuming episodes, and playing streams through a browser-based player. It collects the parts of an anime workflow that usually live across several products and keeps them in one small Go application backed by SQLite. I built it as a portfolio project, but the goal was never to make a disposable demo. The interesting part of the project is the product shape: server-rendered pages, a local database, provider integrations, playback proxying, recommendations, migrations, tests, and a TypeScript player that only appears where browser state actually earns its place. > [!NOTE] > This is a personal, local-first project. It is written to demonstrate product engineering choices, > not to present itself as an official MyAnimeList client or a hosted streaming platform. ## Contents - [What This Project Is](#what-this-project-is) - [What It Includes](#what-it-includes) - [How It Is Built](#how-it-is-built) - [Working Locally](#working-locally) - [Repository Map](#repository-map) ## What This Project Is This project started from a simple idea: anime tracking becomes more interesting when catalog data, personal progress, and playback live in the same interface. A user should be able to discover a title, inspect its metadata, add it to a watchlist, watch an episode, come back later, and continue from the right place without stitching that flow together manually. That makes the app a useful playground for real application concerns. It has authentication, long-lived user state, external APIs, background refresh behavior, migrations, data fixes, cache boundaries, provider-specific code, and enough frontend complexity to justify TypeScript without turning the whole product into a single-page app. The project is also intentionally modest. It uses a single Go server and a SQLite database because those choices make the system easy to run, inspect, and reason about. The architecture is more about clear ownership than novelty: feature packages own their handlers and services, integrations stay at the edges, and the UI is mostly rendered by the server. ## What It Includes | Area | What it does | | --------------- | ------------------------------------------------------------------------------------------------------------ | | Catalog | Browse, search, and inspect anime metadata from external catalog sources. | | Details | Render synopsis, reviews, characters, statistics, relations, themes, and watch-order data. | | Watchlist | Store local user state for saved titles, statuses, and progress-driven flows. | | Playback | Serve watch pages, proxy streams/subtitles, rewrite playlists, and track progress. | | Player | Handle HLS playback, quality selection, subtitles, keyboard controls, episode navigation, and skip segments. | | Recommendations | Generate personal top picks from watchlist signals and recommendation data. | | Maintenance | Run migrations, startup fixes, local user commands, and data repair scripts. |
Implementation notes The backend is written in Go with Gin for HTTP routing and Fx for module wiring. SQLite is used for local persistence, with migrations and data fixes committed alongside the application. Templates are rendered on the server, HTMX handles small partial updates, and TypeScript powers the interactive parts of the browser experience. The most stateful frontend code lives under `static/player`, where the app handles playback mode, source loading, progress storage, subtitles, timelines, quality changes, keyboard shortcuts, skip segments, episode completion, and thumbnail navigation.
## How It Is Built The application is organized around product boundaries rather than framework layers. `internal/anime` owns catalog-facing behavior, `internal/watchlist` owns saved user state, `internal/playback` owns watch data and proxy behavior, and `integrations` contains provider clients. This keeps the core app from depending directly on the details of a specific metadata or playback source. Server-rendered templates are the default because most pages are content-heavy and benefit from simple request-response rendering. TypeScript is used where the browser has real ongoing state: search interactions, theme handling, carousels, watchlist actions, toast messages, and especially the video player. The result is a codebase that behaves like a small product rather than a tutorial project: it has a repeatable toolchain, database evolution, local maintenance commands, focused tests, and a clear split between app code and external integrations. ## Working Locally The local workflow assumes [`mise`](https://mise.jdx.dev/) for tool versions and `just` for common commands. ```bash mise install bun install just dev ``` The development server runs on `http://localhost:3000` by default. `just dev` uses Air to rebuild the Go server and frontend assets when relevant files change. Create a local user with: ```bash go run ./cmd/user ``` ### Commands | Command | Use it for | | ------------------------------- | --------------------------------------------------- | | `just setup` | Install pinned tools and Bun dependencies. | | `just dev` | Run the app locally with live rebuilds. | | `just build` | Build the Go binary, CSS, and TypeScript assets. | | `just test` | Run the Go test suite. | | `just check` | Run linting, tests, typechecking, and a full build. | | `just lint-go` / `just lint-ts` | Run backend or frontend linting separately. | | `just typecheck` | Run TypeScript without emitting files. | | `just run` | Build and run the compiled server. | | `just clean` | Remove generated build output. |
Configuration Configuration is loaded from environment variables, and a local `.env` file is read automatically. | Variable | Default | Purpose | | --------------------------- | --------------- | ------------------------------------------------------------------- | | `PORT` | `3000` | HTTP port for the server. | | `DATABASE_FILE` | `mal.db` | SQLite database path. | | `GIN_MODE` | release default | Gin runtime mode. | | `MAL_CORS_ALLOW_ALL` | disabled | Allows any origin when set to `1`; intended for local/proxy setups. | | `PLAYBACK_PROXY_SECRET` | empty | Enables signed playback proxy tokens when set. | | `EPISODE_AVAILABILITY_MODE` | `auto` | Episode availability strategy: `auto`, `legacy`, or `jikan`. | | `MAL_JIKAN_TRACE` | disabled | Enables optional Jikan client tracing when truthy. |
Maintenance commands | Command | Use it for | | ------------------------ | ---------------------------------------------------------- | | `just new-data-fix name` | Scaffold a new data-fix file. | | `just run-fixes` | Run registered data fixes through `cmd/user`. | | `just fix-all` | Run the Bun maintenance script for data fixes. | | `bun run format` | Format TypeScript and related frontend files with `oxfmt`. |
## Repository Map | Path | Responsibility | | -------------------------------- | --------------------------------------------------------------- | | `cmd/server` | Web server entry point. | | `cmd/user` | Local user and maintenance commands. | | `internal/anime` | Catalog, details, browse, search, reviews, and recommendations. | | `internal/auth` | Authentication, middleware, and local user handling. | | `internal/watchlist` | Watchlist handlers, service logic, and persistence. | | `internal/playback` | Watch data, progress, proxy tokens, and skip segments. | | `internal/episodes` | Episode refresh and provider mapping. | | `internal/database` | SQLite setup, migrations, and startup data fixes. | | `integrations/jikan` | Jikan API client and catalog types. | | `integrations/playback/allanime` | Playback provider client and extraction logic. | | `templates` | Server-rendered pages and reusable components. | | `static` | TypeScript source for client-side behavior. | | `scripts` | Bun-powered development and maintenance scripts. | --- Released under the [MIT License](LICENSE).