Repository navigation
Architecture
Overview of the BookStorage tech stack, project structure, and internal architecture.
| Layer | Technology |
|---|---|
| Language | Go 1.22+ |
| HTTP server | Go net/http standard library with http.ServeMux (Go 1.22 method routing) |
| Templates |
html/template (.gohtml files) |
| Database |
SQLite (github.com/mattn/go-sqlite3, CGO) or PostgreSQL (github.com/lib/pq) |
| Password hashing |
bcrypt (golang.org/x/crypto/bcrypt) |
| OAuth |
Google OAuth 2.0 (golang.org/x/oauth2) |
| Metrics |
Prometheus client (github.com/prometheus/client_golang) |
| Internationalization | JSON locale files embedded via //go:embed
|
| Frontend | Server-rendered HTML, vanilla CSS/JS, no build step |
| PWA | Service worker + web manifest (static/pwa/) |
BookStorage/
├── cmd/bookstorage/ # Application entry point
│ └── main.go # Flags, route registration, HTTP server bootstrap
├── internal/
│ ├── server/ # HTTP handlers (HTML pages + JSON API)
│ │ ├── handlers.go # Core handlers, auth middleware (RequireLogin, RequireAdmin, etc.)
│ │ ├── sessions.go # Session management (cookie, SHA-256, TTL)
│ │ ├── oauth_google.go # Google OAuth flow
│ │ ├── handlers_reading_sites.go # Reading sites CRUD + probe
│ │ ├── import_export.go # Export/import (CSV, JSON, MAL, AniList)
│ │ ├── duplicates.go # Duplicate detection and merging
│ │ ├── csv_import.go # Multi-step CSV import
│ │ ├── work_parent.go # Series/parent work logic
│ │ ├── handlers_admin.go # Admin panel handlers
│ │ ├── prometheus_admin.go # Admin monitoring (Prometheus queries)
│ │ └── ...
│ ├── config/ # Configuration loading
│ │ ├── config.go # Environment variables, validation
│ │ └── site.go # site.json (legal notice)
│ ├── database/ # Data access layer
│ │ ├── database.go # SQLite connection, schema, queries
│ │ ├── postgres_schema.go # PostgreSQL schema (tables, indexes, FTS)
│ │ ├── migrations.go # Numbered schema migrations
│ │ ├── migrate_sqlite_to_pg.go # SQLite to PostgreSQL migration
│ │ └── reading_site.go # Reading site queries
│ ├── catalog/ # External catalog integrations
│ │ └── ... # AniList GraphQL, MangaDex API
│ ├── recommend/ # Recommendation engine (AniList-based)
│ ├── translate/ # LibreTranslate integration
│ └── i18n/ # Internationalization
│ ├── i18n.go # Locale loading, translation functions
│ └── locales/ # JSON locale files (de, en, es, fr, it, pt)
├── templates/ # Go HTML templates (.gohtml)
│ ├── web/ # Desktop web views
│ ├── mobile/ # Mobile PWA views
│ └── shared/ # Shared template partials
├── static/ # Static assets (served at /static/)
│ ├── pwa/ # PWA manifest, service worker, icons
│ ├── images/ # Uploaded cover images (default dir)
│ └── avatars/ # Uploaded avatars (default dir)
├── config/
│ └── site.json.example # Legal notice template
├── deploy/
│ ├── install.sh # Production installer
│ ├── bookstorage.service # systemd unit file
│ └── setup-bookstorage-prometheus.sh # Prometheus setup script
├── scripts/
│ ├── bsctl # Management CLI (bash)
│ └── bsctl.completion.bash # Bash tab completion
├── docs/ # Project documentation
│ ├── self-hosting.md
│ ├── development.md
│ └── fr/ # French translations
├── .github/workflows/
│ ├── ci.yml # CI pipeline
│ └── deploy.yml # Deployment workflow
├── Makefile # Build targets
├── .env.example # Environment variable template
├── .golangci.yml # Linter configuration
└── go.mod / go.sum # Go module dependencies
flowchart TD
Client["Client (Browser / PWA)"] -->|"HTTP request"| Middleware
subgraph MiddlewareChain ["Middleware Chain"]
Middleware["AccessLog"] --> ReqID["RequestID"]
ReqID --> SecHeaders["SecurityHeaders"]
SecHeaders --> ErrPages["ErrorPages"]
ErrPages --> DBCheck["DatabaseUnavailable"]
DBCheck --> Policies["RequestPolicies\n(Origin/Referer, Rate Limit)"]
end
Policies --> Router["http.ServeMux\n(Go 1.22 method routing)"]
Router -->|"public"| PublicHandlers["Public Handlers\n(Home, Login, Register,\nHealthz, Metrics)"]
Router -->|"RequireLogin"| AuthHandlers["Authenticated Handlers\n(Dashboard, API, Profile)"]
Router -->|"RequireAdmin"| AdminHandlers["Admin Handlers\n(Accounts, Monitoring,\nEnrich, Update)"]
Router -->|"RequireSuperadmin"| SuperHandlers["Super-Admin Handlers\n(PostgreSQL Migration)"]
AuthHandlers --> Templates["html/template\n(.gohtml)"]
AuthHandlers --> JSONAPI["JSON API\nResponse"]
AdminHandlers --> Templates
SuperHandlers --> Templates
PublicHandlers --> Templates
Templates --> DB["Database\n(SQLite / PostgreSQL)"]
JSONAPI --> DB
AdminHandlers -->|"loopback"| Prometheus["Prometheus\nHTTP API"]
The HTTP handler is wrapped in several middleware layers (outermost to innermost):
- AccessLog — logs every request (method, path, status, duration)
- RequestID — attaches a unique ID to each request
- SecurityHeaders — sets security headers (CSP, X-Frame-Options, etc.)
- ErrorPages — renders custom HTML error pages
- DatabaseUnavailable — returns 503 if the database is unreachable
- RequestPolicies — origin/referer validation on mutating requests, rate limiting on sensitive endpoints
Routes are registered in cmd/bookstorage/main.go using Go 1.22's method-aware http.ServeMux:
mux.HandleFunc("GET /api/works", app.RequireLogin(app.HandleAPIWorksList))
mux.HandleFunc("POST /api/works", app.RequireLogin(app.HandleAPIWorksCreate))
mux.HandleFunc("PATCH /api/works/{id}", app.RequireLogin(app.HandleAPIWorksUpdate))-
Reading site prober: a goroutine started in
main.gochecks all reading sites every 5 minutes via HTTP probes -
Startup backfill:
BackfillReadingSiteIDslinks existing works to their reading sites at startup
srv := &http.Server{
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second, // BOOKSTORAGE_HTTP_READ_TIMEOUT_SEC
WriteTimeout: 120 * time.Second, // BOOKSTORAGE_HTTP_WRITE_TIMEOUT_SEC
IdleTimeout: 60 * time.Second,
}BookStorage uses embedded JSON locale files for translations:
| Language | Code | File |
|---|---|---|
| English | en |
internal/i18n/locales/en.json |
| French | fr |
internal/i18n/locales/fr.json |
| German | de |
internal/i18n/locales/de.json |
| Spanish | es |
internal/i18n/locales/es.json |
| Italian | it |
internal/i18n/locales/it.json |
| Portuguese | pt |
internal/i18n/locales/pt.json |
The default language is English (DefaultLang = LangEN). Users can switch languages via GET /lang/{lang}, which sets a lang cookie.
Locale files are embedded at compile time using //go:embed locales/*.json.
The PWA manifest is at static/pwa/manifest.json. The mobile view uses simplified templates under templates/mobile/ that focus on the dashboard and chapter tracking.
Mobile-only behavior:
- Pages like Statistics, Profile, Tools, Users, and Admin redirect to the dashboard via
MobileRedirectToDashboard - The app auto-refreshes when brought back to the foreground
Static files under static/ are served at /static/ through a dedicated handler that respects configured upload directories (BOOKSTORAGE_UPLOAD_DIR, BOOKSTORAGE_AVATAR_DIR).
Database — Next: explore the database schema and migrations.