mtvl is a modular, high-performance tracking backend built in Go. It allows developers to create custom tracking categories (such as movies, tv shows, books, video games, anime, etc.) through a pluggable module system and Goose SQL database migrations.
- Extensible Category Modules: Each category (e.g., movies, tv shows, books) lives in its own repository under
mylists/implementingcore.CategoryModule. - Dedicated Category Endpoints & Standalone Services: Each category has its own API endpoint and can run as an independent microservice or hooked into
mtvl. - Category-Owned Migrations: Database migrations and category details belong to each respective category repo.
- Shared Functions in mtvl:
mtvlprovides shared auth, idgen, core registry, and references each category in user lists (/api/v1/user/lists). - Dynamic Category Discovery: Central registry automatically exposes active modules via GET
/api/v1/categories.
make test
# or: go test -v ./...# PostgreSQL (default)
go run main.go
# Custom PostgreSQL DSN
DB_DRIVER=postgres DB_DSN="postgres://user:pass@localhost:5432/mtvl?sslmode=disable" go run main.go
# MySQL
DB_DRIVER=mysql DB_DSN="user:pass@tcp(localhost:3306)/mtvl?parseTime=true" go run main.goAdding a new tracking category requires only 3 steps:
Create migrations/postgres/20260804191240_books.sql (and migrations/mysql/20260804191240_books.sql):
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS books (
id CHAR(36) PRIMARY KEY,
user_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
author VARCHAR(255) DEFAULT '',
pages_read INTEGER DEFAULT 0,
total_pages INTEGER DEFAULT 0,
status VARCHAR(50) DEFAULT 'plan_to_read',
rating INTEGER DEFAULT 0,
notes TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS books;
-- +goose StatementEndCreate ../<category>/handler.go (e.g. books/handler.go):
package books
import (
"net/http"
"github.com/go-chi/chi/v5"
"gorm.io/gorm"
"github.com/mylists/mtvl/pkg/core"
)
type Module struct { db *gorm.DB }
func NewModule(db *gorm.DB) *Module { return &Module{db: db} }
func (m *Module) Info() core.CategoryInfo {
return core.CategoryInfo{
Category: "books",
DisplayName: "Books",
Description: "Track reading progress and books",
Endpoint: "/api/v1/books",
}
}
func (m *Module) RegisterRoutes(r chi.Router, authMw func(http.Handler) http.Handler) {
r.Route("/api/v1/books", func(sub chi.Router) {
sub.Get("/", m.listItems) // public catalog search
sub.Get("/{id}", m.getItem)
sub.Group(func(protected chi.Router) {
protected.Use(authMw)
// Attach catalog writes and personal-list handlers...
})
})
}In main.go, add:
registry.Register(books.NewModule(database))That's it! The new category will automatically show up in /api/v1/categories and its endpoints will be protected by your auth middleware.
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/health, /healthz |
Combined health check (includes Postgres) | No |
GET |
/livez, /health/live |
Kubernetes liveness probe | No |
GET |
/readyz, /health/ready |
Kubernetes readiness probe (includes Postgres) | No |
GET |
/startupz, /health/startup |
Kubernetes startup probe (includes Postgres) | No |
POST |
/api/v1/auth/register |
Register new user | No |
POST |
/api/v1/auth/login |
Login user & get JWT token | No |
GET |
/api/v1/auth/me |
Get current user info | Yes |
GET |
/api/v1/categories |
Discover registered tracking modules | No |
GET |
/api/v1/search?q={query} |
Search the public catalog across all categories | No |
GET |
/api/v1/movies |
Search/list public movie catalog (q, pagination supported) |
No |
POST |
/api/v1/movies |
Create movie record | Yes |
GET |
/api/v1/movies/{id} |
Get single movie | No |
PUT |
/api/v1/movies/{id} |
Update movie | Yes |
DELETE |
/api/v1/movies/{id} |
Delete movie | Yes |
GET |
/api/v1/tvshows |
Search/list public TV show catalog | No |
POST |
/api/v1/tvshows |
Create TV show record | Yes |
GET |
/api/v1/tvshows/{id} |
Get single TV show | No |
PUT |
/api/v1/tvshows/{id} |
Update TV show | Yes |
DELETE |
/api/v1/tvshows/{id} |
Delete TV show | Yes |
GET |
/api/v1/books |
Search/list public book catalog | No |
POST |
/api/v1/books |
Create book record when it is absent from the catalog | Yes |
GET |
/api/v1/books/{id} |
Get single book | No |
PUT |
/api/v1/books/{id} |
Update book | Yes |
DELETE |
/api/v1/books/{id} |
Delete book | Yes |