Songloft Backend Architecture
Tech Stack
- Go version: 1.26+
- Web framework: Chi v5.2.4
- Authentication: JWT dual-token authentication (Access Token + Refresh Token)
- Database: SQLite 3 (modernc.org/sqlite v1.46.1, pure-Go CGO-free implementation)
- Database access stack:
pressly/goose v3— schema migrations (autoUpon startup, files inmigrations/000N_xxx.sql)sqlc-dev/sqlc— generates type-safe code from fixed SQL (queries/*.sql→sqlc/*.sql.go, generated at CLI time)Masterminds/squirrel v1.5— dynamic SQL construction (variable-length WHERE/SET/ORDER/pagination)- Self-built Repository + UnitOfWork layer; transactions done via
db.RunInTx(ctx, func(ctx, *UnitOfWork))
- Metadata read/write: hanxi/tag (a dhowden/tag fork, with enhanced encoding detection; adds MP3 ID3v2.3 and FLAC Vorbis Comment writing)
- Audio analysis: ffprobe (optional, used to obtain precise technical parameters)
- JS runtime: QuickJS (modernc.org/quickjs, pure-Go implementation, used for JS plugin script execution)
- Plugin architecture: JS script plugins (QuickJS sandbox + permission model + health checks + hot reload)
- Monitoring: Tracely client (heartbeats, install/upgrade statistics, panic capture)
Architecture Design
Layered Architecture
HTTP Server (main.go)
→ Config (internal/config/)
→ Routes + Middleware (internal/app/routers.go + internal/middleware/)
→ Handlers (internal/handlers/)
→ Services (internal/services/)
│
├── Database main path
│ → Repository / UnitOfWork (internal/database/*_repository.go, unit_of_work.go)
│ → sqlc fixed SQL (internal/database/sqlc/) + squirrel dynamic SQL
│ → SQLite (data/songloft.db, schema managed by goose migrations)
│
└── JS plugin path (on demand)
→ JS Plugin Manager (internal/jsplugin/)
→ JS Runtime — QuickJS sandbox (internal/jsruntime/)Services and Database form the core data flow; JS plugins are a side-chain capability (HTTP is forwarded to
jsplugin.Managerand then enters the QuickJS sandbox), not on the main write path.
Package Structure
internal/ directory
Holds the project's core business logic, organized by functional module:
app/ - Application entry and configuration
app.go: The application's main struct (App) and initialization logic, including dependency injection, service creation, and signal handlingrouters.go: Route configuration and registration, defining all API routes and the middleware chainrouter_dev.go: Development routes (includes Swagger,-tags dev)router_prod.go: Production routes (excludes Swagger)embed.go: Flutter Web frontend static asset serving, with SPA route fallback (returnsindex.htmlwhen the requested file does not exist)embed_common.go: Shared utilities for static file serving (Base62 decoding, path safety validation, and other internal helpers; the legacy/music/*and/cover/*Base62 paths have been retired in favor of/api/v1/songs/{id}/play|cover|lyric)pprof_dev.go: pprof endpoints in development mode (-tags dev)source_adapters.go: Adapts the services-layer implementations to the interfaces defined in theservices/source/subpackage (fetcher / resolver / validator, etc.)
config/ - Configuration type definitions
types.go: The application configuration structAppConfig(port, database path, username/password, etc.)
handlers/ - Request handlers
auth.go: Authentication-related requests (login, token refresh, logout, token management)music.go: Song CRUD, bulk deletion, lyric updatesplaylist.go: Playlist CRUD, song ordering, cover upload, auto-created playlistsconfig.go: Configuration managementscan.go: Scan management (async scanning, progress query, scan cancellation)jsplugin.go: JS plugin management (upload.jsplugin.zip, enable/disable, delete, check for updates)upgrade.go: Version upgrades (check for updates, perform upgrade, reset base image)proxy.go: Resource proxying (works around external CDN CORS restrictions, supports streaming forwarding and Range requests). IncludesServeRemoteResourceWithCache, which streams upstream audio to the client and triggers background cachingcache.go: Music cache management (statistics, cleanup, configuration, custom directory validation)version.go: Version informationhealth.go: Health checksresponse.go: Utility functions for unified JSON responses and error responses
middleware/ - Middleware
auth.go: JWT authentication middleware, validates the Access Tokenauth_test.go: Authentication middleware tests
models/ - Data models
models.go: Core data structures (Song, Playlist, Config, AuthToken, JSPlugin, etc.) and validation logicconstant.go: Pagination limit constants (DefaultPaginationLimit, MaxPaginationLimit)models_test.go: Model validation tests
database/ - Database layer (Repository + UnitOfWork + sqlc + goose)
database.go: TheDBinterface (Close / RunInTx / each *Repository()getter)sqlite.go: TheSQLiteDBimplementation (Open()runs goose Up plus WAL/busy_timeout and other pragmas;RunInTxtransaction wrapper)unit_of_work.go: TheUnitOfWorkstruct, a set of Repositories scoped to a transaction (theSongs / Playlists / PlaylistSongsfields, all bound to the same*sql.Tx)errors.go: Domain errors (sentinels such asErrNotFound/ErrConflict)filters.go: Shared squirrel helpers (sort whitelist,applyOrder,applyPagination)config_repository.go: Config repository (ConfigRepository)song_repository.go: Song repository (includesUpsertRemoteSong: reuses an existing ID on a(plugin_entry_path, dedup_key)hit, and falls back to a direct INSERT when dedup_key is empty)playlist_repository.go: Playlist repositoryplaylist_song_repository.go: Playlist-song association repository (includesReplaceSong, etc.)token_repository.go: Authentication token repositoryjsplugin_repository.go: JS plugin repositorymigrations/: goose migration source files (0001_init.sql, etc., bundled viaembed.FSand auto-Up'd on startup)queries/: sqlc inputs (one*.sqlper table; runmake sqlcto generate code)sqlc/: sqlc outputs (*.sql.go, checked into the repo, no sqlc CLI dependency at runtime)testutil/:OpenMemoryDB(t)starts a:memory:SQLite that runs real migrations plus real Repositories, for use in testssqlite_test.go: Database-layer integration tests
services/ - Business logic layer
auth_service.go: Authentication service (JWT dual-token generation/verification, token management, secret generation)config_service.go: Configuration service (database config management, supports reading/writing JSON format)metadata.go: Metadata extraction service (uses hanxi/tag to extract tags and covers, ffprobe for technical parameters). Title strategy: prefer the tag's title when present, and fall back to the filename only when it's missing (no more longest-common-substring concatenation)scanner.go: File scanning service (recursively scans the music directory, supports directory exclusion and format filtering)scan_progress.go: Scan progress tracking (async scan state management)song_service.go: Song service (CRUD, bulk operations, duration backfill)playlist_service.go: Playlist service (CRUD, song management, auto-creation)upgrade_service.go: Version upgrade service (fetch version info, perform upgrade, reset)cache_service.go: Music cache service (LRU eviction, custom cache directory, capacity limit configuration)cache_service_song.go: Song-level helpers for the cache service (hit lookup, concurrent-download deduplication, associated cleanup, streaming-proxy callbacks, etc.)cache_path_template.go: Path template rendering (placeholders such as{artist}-{album}/{title}, used by caching and plugin persistence)cache_metadata_writer.go: File metadata embedding (tag writing + remote cover fetching, used by plugin persistence)song_downloader.go: Song persistence service (plugin infrastructure: persists remote songs to the localmusic_pathvia thesongs.downloadBridge API)internal_url.go: Internal loopback URL construction (assembles relative URLs intohttp://127.0.0.1:{port}/...?access_token=..., for convert/cache to call plugins)whitelist.go: Domain whitelist validation (SSRF protection, blocks access to intranet addresses)source/: Audio source adapter subpackage —fetcher(HTTP data retrieval + URL parsing),resolver(cross-plugin fallback),validator(parameter validation),orchestrator(orchestration, includesResolveURLwhich only resolves without downloading),metrics(metrics). See the interface bindings ininternal/app/source_adapters.gofor the concrete implementations
jsplugin/ - JS plugin management layer
plugin.go: JS plugin runtime model and state machinemanager.go: JS plugin manager (lifecycle, async loading, sub-route registration)loader.go: Unpacks.jsplugin.zip/ validates the manifest / parses permissionspackage.go: Install/update/uninstall flow (includes hash verification)repository.go: Repository interface (implementation indatabase/jsplugin_repository.go)api_bridge.go: Host API bridge (exposes http, storage, logger, songs, playlists, etc. to QuickJS, including thesongs.downloadpersistence capability)communication.go: Host ↔ plugin call protocol wrapper (request/response serialization)invoke.go: Unified wrapper for calling plugin entry functions (with timeout and error normalization)hash.go: File fingerprint utility (used for hot_reload and package verification)scheduler.go: Scheduler (avoids VM concurrency races)health.go: Health checks (probes viajsruntime.HealthProbe, auto-isolates on failure)hot_reload.go: Hot reload (auto-reloads based on file hash fingerprints)permissions.go: Permission model validationservice.go: Plugin instance service shellroutes.go: Sub-route mounting
jsruntime/ - JavaScript runtime
runtime.go: QuickJS runtime environment management (JSEnv), supports parallel calls, event collection, and timeout controlpolyfill.go: JS polyfill code (console, setTimeout/setInterval, Function.toString, etc.)pendingjob.go: Low-levelJS_ExecutePendingJobcalls (handles Promise microtasks)
version/ - Version information
version.go: Version number, Git Commit, build time, build type (injected via-ldflags)
pkg/ directory
Holds reusable public packages:
tag/ - Audio metadata read/write library
- Reading: MP3 (ID3v1/ID3v2.2/2.3/2.4), FLAC, OGG/Vorbis, M4A/MP4, WAV, DSF formats; cover images, lyrics, encoding detection
- Writing (
WriteTag(filePath, opts)):- ✅ MP3 ID3v2.3: TIT2/TPE1/TPE2/TALB/TYER/TCON/USLT/APIC
- ✅ FLAC: Vorbis Comment + PICTURE block
- ⚠️ M4A/MP4: returns
ErrUnsupportedWrite(TODO, requires rebuilding moov + updating stco) - ⚠️ OGG: returns
ErrUnsupportedWrite(TODO, requires re-paging + recomputing CRC) - Temp file +
os.Rename, atomic overwrite
- Command-line tools:
cmd/tag,cmd/sum,cmd/check
Build System
Build Tags
| Tag | Description | Purpose |
|---|---|---|
dev | Development mode | Includes Swagger docs + pprof |
lite | Lite mode | Does not embed the frontend, smaller binary |
| No tag | Full mode (default) | Embeds the Flutter Web build output into the binary |
Frontend Embedding Mechanism
web_embed.go (build tag: !lite) → //go:embed all:songloft-player-build/web-embedded
web_embed_lite.go (build tag: lite) → empty embed.FSDesign Patterns
Dependency Injection
// Inject dependencies through the constructor
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
return &AuthHandler{
authService: authService,
}
}Interface Abstraction
database.DB only exposes the transaction entry point and the per-Repository getters; all CRUD logic is pushed down into the Repositories:
type DB interface {
Close() error
RunInTx(ctx context.Context, fn func(context.Context, *UnitOfWork) error) error
SongRepository() *SongRepository
PlaylistRepository() *PlaylistRepository
PlaylistSongRepository() *PlaylistSongRepository
ConfigRepository() *ConfigRepository
TokenRepository() *TokenRepository
JSPluginRepository() *JSPluginRepository
}The service layer injects the database.DB interface; single-table writes go directly through db.SongRepository().Create(...), while cross-table writes go through db.RunInTx(ctx, func(ctx, uow *UnitOfWork) error { uow.Songs.Create(...); uow.PlaylistSongs.ReplaceSong(...) }). See database_migrations.md for details.
Tests no longer hand-write mocks; they uniformly use
database/testutil.OpenMemoryDB(t)to spin up a:memory:SQLite that runs real migrations and real Repositories.
API Design
The backend provides a RESTful API, mainly including:
/api/v1/auth/*- Authentication endpoints (login, refresh, logout, token management)/api/v1/songs/*- Song management endpoints (CRUD, bulk deletion, lyric updates)/api/v1/playlists/*- Playlist management endpoints (CRUD, song ordering, cover upload, auto-creation)/api/v1/configs/*- Configuration management endpoints/api/v1/jsplugins/*- JS plugin management endpoints (upload.jsplugin.zip, enable/disable, delete, check for updates)/api/v1/jsplugin/{entry_path}/*- JS plugin runtime routes (registered by the plugin's main.js via the SDK Router)/api/v1/scan/*- Scan management endpoints (async scanning, progress query, cancellation)/api/v1/upgrade/*- Version upgrade endpoints (available only in Docker environments, includes reset)/api/v1/proxy- Resource proxy endpoint (works around CORS, includes SSRF protection)/api/v1/cache-manage/*- Music cache management (statistics/cleanup/configuration/directory validation)/api/v1/settings/hls-proxy- HLS radio proxy toggle (GET/PUT)/api/v1/settings/http-proxy- General HTTP proxy configuration (GET/PUT)/api/v1/settings/music-path- Music path and scan exclusions (GET/PUT)/api/v1/settings/plugin-registries- Plugin subscription source list (GET/PUT)/api/v1/settings/log-level- Log level (GET/PUT)/api/v1/settings/scan-auto-create-include-subdirs- Whether scan auto-created playlists include subdirectories (GET/PUT)/api/v1/version- Version information endpoint/api/v1/health- Health check endpoint
In addition, music files, cover images, and lyrics are accessed via song-ID endpoints (which require the access_token query parameter for authentication):
/api/v1/songs/{id}/play— Streams the audio (supports the local / remote / radio types + Range)/api/v1/songs/{id}/cover— Song cover (local songs are served by this backend; for network songs,MarshalJSONreturns the original CDN URL directly)/api/v1/songs/{id}/lyric— Plain-text LRC lyrics
The old
/music/*and/cover/*Base62-encoded path scheme has been retired; the Base62 helper inembed_common.gohas been reduced to an internal utility and no longer registers routes.
For detailed API documentation, refer to the Swagger docs (visit /swagger/index.html in a development environment).
