ZeroNime Backend is a Go service for anime catalog discovery, episode metadata, stream resolution, and playback-oriented media proxying.
The service is designed around one practical goal: make first-frame playback fast enough for browser video players even when the upstream provider is slow, inconsistent, or uses short-lived signed URLs.
This repository contains the backend only. Frontend apps, production secrets, local cache volumes, and machine-specific deployment files are intentionally excluded from the public setup.
- Serves an
api/v2JSON API for search, schedules, catalog pages, watchlist, history, and continue-watching. - Resolves provider-specific episode pages into playable media candidates.
- Proxies images and media through controlled endpoints instead of exposing raw provider behavior directly to the client.
- Uses a startup media cache so browsers can begin playback without waiting for a full upstream fetch.
- Supports optional object storage backing for cache durability across restarts and deployments.
Anime providers often serve direct MP4 files behind unstable hosts or signed URLs. In practice, the first playback request is where most latency comes from:
- the player needs MP4 metadata quickly,
- the upstream host may be slow to deliver the first bytes,
- the URL may expire between resolution and playback,
- and multiple browsers will often request overlapping byte ranges at startup.
This backend addresses that by caching only the parts of the file that matter most during startup.
For each episode, the backend can store two partial byte ranges:
head— the first bytes of the MP4 filetail— the last bytes of the MP4 file
With the default configuration:
head = 10 MBtail = 2 MB
Why both?
- The beginning of the file usually contains bytes the player needs immediately for startup.
- The end of the file often contains MP4 metadata, including the
moovatom, which some files place near the tail instead of the head.
By caching only these two regions, the backend can satisfy the browser's early requests much faster than fetching the entire file up front.
This is the simplest mode and works without DObject.
How it works:
- startup chunks are stored on local disk under
ANIME_MEDIA_CACHE_DIR /api/v2/mediareads from local cache first- cache misses fall back to upstream
- if the backend restarts or the disk is cleared, the cache is rebuilt on demand
Use this mode when:
- you are developing locally
- you run a single backend instance
- you do not need cache persistence across redeploys
This is the recommended mode if you want durable cache storage.
How it works:
- local disk remains the fastest read path
- DObject acts as a persistent backing store for cached startup chunks
- if local cache is empty, the backend can restore from DObject instead of re-fetching from the upstream provider
- this reduces startup penalties after container restarts or redeploys
Use this mode when:
- you want startup cache to survive restarts
- you are running in containers or ephemeral environments
- you want to reduce repeated upstream downloads over time
The backend also supports a short-lived local predictive cache for the next episodes in a watching session.
In practice:
- while the current episode is already playing
- the backend can prepare startup chunks for the next one or two episodes
- those predictive chunks are stored locally and rotated out as the viewing window moves
This cache is intentionally temporary and session-oriented. It is separate from the main startup cache strategy.
No. DObject is optional.
If all DOBJECT_* variables are left empty, the backend will run in local-only mode and still function correctly.
You should think of DObject as a durability layer, not a hard dependency:
- without DObject: faster setup, fewer moving parts, cache disappears with local storage
- with DObject: better cache persistence, better restart behavior, more infrastructure to manage
The public .env.example keeps DObject disabled by default.
Key packages:
cmd/server— HTTP server entrypointcmd/prewarm-startup— CLI for prewarming startup cachecmd/purge-startup-cache— CLI for clearing startup cacheinternal/config— environment parsing and runtime configurationinternal/httpserver— routes, middleware, public handlersinternal/provider— provider contracts and implementationsinternal/service/catalog— catalog aggregation and cache-aware catalog logicinternal/service/stream— stream candidate selection and stream cache behaviorinternal/service/library— watchlist, history, continue-watchinginternal/store/postgres— PostgreSQL persistenceinternal/mediacache— startup chunk cache implementationinternal/mediaproxy— media proxy behavior for ranged playback
Public endpoints:
GET /healthPOST /api/v2/session/anonymousGET /api/v2/homeGET /api/v2/search?q=...GET /api/v2/scheduleGET /api/v2/indexGET /api/v2/catalog/:catalogIdGET /api/v2/catalog/:catalogId/episodesGET /api/v2/stream/:episodeIdGET /api/v2/media?...GET /api/v2/image?url=...
Endpoints requiring X-Client-Token:
GET /api/v2/watchlistPOST /api/v2/watchlistDELETE /api/v2/watchlist/:catalogIdGET /api/v2/historyPOST /api/v2/historyDELETE /api/v2/history/:catalogIdGET /api/v2/continue-watching
- Go
1.26+ - PostgreSQL
15+ - Redis
7+if you want Redis-backed cache behavior - Chromium if your active provider requires browser rendering outside Docker
- Copy the example environment file:
cp .env.example .env- Start the local stack:
docker compose -f docker-compose.local.yml up -d --build- Smoke test:
curl -s http://127.0.0.1:8080/health
curl -s -X POST http://127.0.0.1:8080/api/v2/session/anonymous
curl -s "http://127.0.0.1:8080/api/v2/search?q=naruto"- Start PostgreSQL and Redis yourself.
- Copy
.env.exampleto.envand adjust values as needed. - Load the environment and run the server:
set -a
source .env
set +a
go run ./cmd/serverThe complete reference is in .env.example. The variables are grouped by responsibility so the setup remains easy to reason about.
ANIME_LISTEN_ADDR— HTTP listen addressDATABASE_URL— PostgreSQL connection stringREDIS_ADDR,REDIS_PASSWORD,REDIS_DB— optional Redis settings
ANIME_ACTIVE_PROVIDER— currently active provider implementationOTAKUDESU_BASE_URL— Otakudesu base URLKURAMANIME_BASE_URL— Kuramanime base URLANIME_BROWSER_PATH— browser executable path for providers that need rendering
ANIME_REQUEST_TIMEOUTANIME_CACHE_TTLANIME_STREAM_CACHE_TTLANIME_IMAGE_CACHE_TTLANIME_BROWSER_RENDER_BUDGETANIME_PUBLIC_RATE_LIMIT_RPMANIME_WRITE_RATE_LIMIT_RPM
ANIME_MEDIA_CACHE_DIR— local storage directory for startup chunksANIME_MEDIA_CACHE_HEAD_BYTES— number of bytes cached from the file headANIME_MEDIA_CACHE_TAIL_BYTES— number of bytes cached from the file tailANIME_MEDIA_CACHE_FETCH_TIMEOUT— timeout when building startup cache from upstream
ANIME_PREDICTIVE_CACHE_ENABLED— enables or disables predictive startup cachingANIME_PREDICTIVE_CACHE_DIR— local directory for predictive startup chunksANIME_PREDICTIVE_CACHE_HEAD_BYTES— head chunk size for predictive cacheANIME_PREDICTIVE_CACHE_TAIL_BYTES— tail chunk size for predictive cache
DOBJECT_URLDOBJECT_S3_ACCESS_KEYDOBJECT_S3_SECRET_KEYDOBJECT_BUCKETDOBJECT_REGIONDOBJECT_FORCE_PATHDOBJECT_AUTO_CREATEDOBJECT_USE_WHEN_READY
If DOBJECT_URL, access key, or secret key are empty, the backend stays in local-only mode.
An example Caddy config is available at Caddyfile.example.
For public deployment, copy it to Caddyfile and replace api.example.com with your own domain.
.env, deployment notes, local volumes, and cache directories are ignored by.gitignore.docker-compose.local.ymlis intended for local development.docker-compose.ymlis a fuller stack example with additional services such as reverse proxy.
go test ./cmd/... ./internal/...No license is included by default. Add one before publishing if you want to permit reuse.