Skip to content

Architecture

Luc Garrabos edited this page May 6, 2026 · 1 revision

Architecture

Overview of the BookStorage tech stack, project structure, and internal architecture.


Table of contents


Tech stack

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/)

Project structure

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

Request lifecycle

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"]
Loading

Internals

Middleware

The HTTP handler is wrapped in several middleware layers (outermost to innermost):

  1. AccessLog — logs every request (method, path, status, duration)
  2. RequestID — attaches a unique ID to each request
  3. SecurityHeaders — sets security headers (CSP, X-Frame-Options, etc.)
  4. ErrorPages — renders custom HTML error pages
  5. DatabaseUnavailable — returns 503 if the database is unreachable
  6. RequestPolicies — origin/referer validation on mutating requests, rate limiting on sensitive endpoints

Route registration

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))

Background processes

  • Reading site prober: a goroutine started in main.go checks all reading sites every 5 minutes via HTTP probes
  • Startup backfill: BackfillReadingSiteIDs links existing works to their reading sites at startup

HTTP server configuration

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,
}

Internationalization

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.


PWA and static assets

Progressive Web App

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 file serving

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.

Clone this wiki locally