Development Guide
This document is based on the following source files:
- AGENTS.md -- Project entry information: common commands, database standards, coding conventions, API documentation standards, Git conventions, build and deployment
- Makefile -- All Make targets for build, test, code generation, Docker, etc.
- Dockerfile -- Multi-stage Docker build flow and runtime environment
- sqlc.yaml -- sqlc code generation configuration (engine, input/output paths, generation options)
- go.mod -- Go module declaration, version requirements, and dependency list
Table of Contents
- Development Environment Setup
- Common Commands
- Coding Conventions
- Database Operation Standards
- API Documentation Standards
- Testing Strategy
- Build Variants
- Git Commit Conventions
1. Development Environment Setup
1.1 Backend Dependencies
| Tool | Version Requirement | Description |
|---|---|---|
| Go | 1.26 | The minimum version declared in go.mod |
| SQLite | Automatically embedded at runtime | Uses modernc.org/sqlite (pure Go implementation), no system C library installation required |
| ffmpeg / ffprobe | Any recent version | Audio transcoding and metadata probing |
| sqlc | latest | SQL code generation: go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest |
| swag | latest | Swagger documentation generation (make swagger installs it automatically) |
| UPX | Optional | Automatically compresses the binary during production builds; skipped if not installed |
1.2 Frontend Dependencies
| Tool | Version Requirement | Description |
|---|---|---|
| Flutter | 3.29+ | Cross-platform UI framework |
| Dart | 3.7+ | Bundled with Flutter |
The frontend code is located in songloft-player/ (a separate repository); you need to clone it or initialize the submodule first before development.
1.3 Quick Verification
make deps # Download Go dependencies
make version # Check Go version
make run # Start dev mode (admin/admin, port 58091)
# Visit http://localhost:58091/swagger/index.html to verifySection sources
- go.mod:1-3 -- Module name and Go version requirement
- Makefile:3-4 --
CGO_ENABLED=0andGO_VERSION - Dockerfile:59-69 -- Runtime image dependencies
2. Common Commands
2.1 Backend Commands
| Command | Purpose |
|---|---|
make run | Start dev mode (with Swagger + pprof, default admin/admin/58091) |
make build | Build the dev version (full, with frontend embedded, -tags dev) |
make build-lite | Build the dev version (lite, without frontend embedded, -tags "dev,lite") |
make build-prod | Build the production version (full, with frontend embedded) |
make build-prod-lite | Build the production version (lite, without frontend embedded) |
make test | Run all tests |
make test-short | Quick tests (skip integration tests) |
make test-unit | Only unit tests under internal/ |
make check | One-click check: fmt + vet + test |
make sqlc | Regenerate sqlc code (must run after changing queries/*.sql) |
make swagger | Regenerate the Swagger documentation |
make bump TYPE=patch | Bump the version number and tag it |
2.2 Frontend Build Commands
Frontend build artifacts are uniformly output to songloft-player-build/ (not songloft-player/build/).
| Command | Purpose |
|---|---|
make build-frontend-web-embedded | Embedded mode Web (hides the API address UI) |
make build-frontend-web | Standalone deployment Web |
make build-frontend-{linux,windows,macos,android,ios} | Clients for each platform |
make build-frontend-all | All platforms supported by the current system |
2.3 Frontend Development
cd songloft-player && flutter run -d chrome # standalone
cd songloft-player && flutter run -d chrome --dart-define=DEPLOY_MODE=embedded # embeddedSection sources
- Makefile:97-108 --
build/build-litetargets - Makefile:57-95 -- Frontend build targets
- AGENTS.md:30-49 -- Common commands quick reference
3. Coding Conventions
3.1 Project Structure
The backend follows the standard Go layout, with internal/ preventing direct imports by external packages:
internal/
├── app/ # Application entry: initialization, route registration
├── database/ # Database opening, migration, Repository, UnitOfWork
├── handlers/ # HTTP handler layer
├── middleware/ # Authentication, logging, and other middleware
├── models/ # Data model definitions
├── services/ # Business logic layer
└── ...3.2 Core Conventions
- Routing: Chi v5 + JWT dual token, with route registration concentrated in
internal/app/routers.go - Dependency injection: the service layer only receives Repository interfaces, not the
DBinstance - Logging: the standard library
slog, no third-party framework introduced - HTTP errors: uniformly use the
respondErrorfunction - API response format: RESTful direct return, no
{code, data, message}envelope; errors uniformly{"error": "...", "detail": "..."}
3.3 Data Access Layer (No ORM)
| Scenario | Tool | Description |
|---|---|---|
| Fixed SQL | sqlc | Written in database/queries/*.sql, make sqlc generates the Go code |
| Dynamic SQL (variable-length WHERE/SET) | squirrel | Used in *_repository.go; string concatenation is prohibited |
| Cross-table writes (transactions) | RunInTx + UnitOfWork | Operate on multiple Repositories under the same *sql.Tx |
Section sources
- AGENTS.md:70-77 -- Backend coding conventions
- go.mod:6-10 -- Core dependencies (squirrel, chi, jwt)
4. Database Operation Standards
The content of this section constitutes a project iron law that all developers must strictly follow. The complete access stack: goose migration → sqlc fixed SQL → squirrel dynamic SQL → Repository → UnitOfWork.
4.1 Schema Changes (Migrations)
- Migration files are placed in
internal/database/migrations/000N_xxx.sql goose.Upruns automatically at startup, no manual operation needed- Prohibited to manually
ALTER data/songloft.dbdirectly
4.2 Fixed SQL (sqlc)
- Write SQL in
internal/database/queries/{table}.sql, then runmake sqlcafter modifying - Generated artifacts are in
internal/database/sqlc/and must be committed
Key sqlc configuration (sqlc.yaml) options: engine: sqlite, schema pointing to the migrations directory, emit_interface: true to generate interfaces for easier testing, emit_empty_slices: true to return [] instead of nil for empty results.
4.3 Dynamic SQL and Transactions
- Variable-length
WHERE/SETare built with squirrel in*_repository.go; string concatenation is prohibited - Cross-table writes must use
db.RunInTx(ctx, func(ctx, uow)); UnitOfWork provides all Repositories under the same transaction - Prohibited for the service layer to manually
BeginTx, otherwise SQLite willSQLITE_BUSYdeadlock
4.4 Error Semantics and Built-in Data
- The Repository uniformly returns
database.ErrNotFoundon a miss; the service useserrors.Isto distinguish it - Migrations preset the playlists id=1 "Favorites", id=2 "Radio Favorites", along with the
music_path/jwt_secret/source_*default config -- remember to subtract these when asserting row counts in tests
Section sources
- AGENTS.md:54-66 -- Database standards (iron law)
- sqlc.yaml:1-17 -- Complete sqlc configuration
- go.mod:6 -- squirrel dependency; go.mod:14 -- goose dependency
5. API Documentation Standards
The content of this section constitutes a project iron law. API documentation is generated by swaggo/swag from code comments and is the single source of truth for frontend development and external integration.
5.1 Core Principle
Any handler registered in internal/app/routers.go (including its sub-registration functions) must have swag comments. No exemptions. Even dynamic routes, catch-alls, and reverse-proxy endpoints must have them written -- it suffices to explain in the @Description.
5.2 Required Fields (at least 7 per handler)
// @Summary <one-line summary>
// @Description <detailed description; state side effects/default values/error code trigger conditions>
// @Tags <business group>
// @Produce json
// @Success 200 {object} <return type> "<description>"
// @Security BearerAuth
// @Router /<path> [<method>]Additional fields: for endpoints with a request body, add @Accept json + @Param ... body; for endpoints with an error path, add @Failure; public endpoints omit @Security.
5.3 Business Tag List
Reuse existing tags; do not casually create new tags: Song Management, Playlist Management, Radio and HLS, Scan Management, Config Management, Cache Management, JS Plugin, Data Backup, Settings, Upgrade, Authentication.
5.4 Multiple Aliases and Catch-all Routes
- Multiple alias paths → one
@Routerline each r.HandleFunc(...)catch-all → list all actual methods, one@Routerline each- Dynamic paths → note in the
@Descriptionthat "it is a dynamic route, OpenAPI serves only as a placeholder"
5.5 Post-Modification Verification Flow
make swagger # Generate the documentation
grep '<your-new-path>' docs/swagger.json # Confirm the new path exists
make run # Visually inspect in the Swagger UIThe generated docs/swagger.json, docs/swagger.yaml, and docs/docs.go must be committed.
Section sources
- AGENTS.md:82-123 -- API documentation standards (iron law)
- Makefile:329-337 --
make swaggertarget
6. Testing Strategy
6.1 Core Principles
- Test files
*_test.goare in the same directory as the source code under test - Prohibited to hand-write a mock DB -- use
testutil.OpenMemoryDB(t)to run a real:memory:SQLite + real Repository
func TestSongService(t *testing.T) {
db := testutil.OpenMemoryDB(t)
repo := database.NewSongRepository(db)
svc := services.NewSongService(repo)
// ... test real behavior
}Advantages: covers the real SQL path, in-memory DB starts extremely fast, and automatically runs goose migrations to guarantee schema consistency.
6.2 Notes
- Migrations preset built-in playlists and default config; must be subtracted when asserting row counts
- The dependency injection design ensures the service can be tested independently, without needing to (and without a reason to) mock the database layer
6.3 Test Commands
make test # All tests
make test-short # Skip integration tests
make test-unit # Only internal/
make test-coverage # Coverage report
make bench # Benchmark testsSection sources
- AGENTS.md:65-66 -- Testing standards
- Makefile:206-234 -- Test Make targets
7. Build Variants
7.1 Two Orthogonal Dimensions
| Dimension | Values | Meaning |
|---|---|---|
| VERSION | dev / X.Y.Z | dev automatically enables -tags dev (Swagger + pprof) |
| BUILD_TYPE | lite / empty (full) | lite does not embed the frontend resources |
Prohibited to mix them (such as BUILD_TYPE=dev).
7.2 Build Matrix
| VERSION | BUILD_TYPE | Make Target | build tags |
|---|---|---|---|
| dev | full | make build | dev |
| dev | lite | make build-lite | dev,lite |
| X.Y.Z | full | make build-prod | none |
| X.Y.Z | lite | make build-prod-lite | lite |
7.3 CGO and Cross-Compilation
The Makefile globally sets CGO_ENABLED=0, SQLite uses the pure Go implementation (modernc.org/sqlite), and all-platform cross-compilation is supported:
make build-cross GOOS=linux GOARCH=amd64 OUTPUT=songloft-linux-amd64
make build-cross GOOS=linux GOARCH=arm64 OUTPUT=songloft-linux-arm64
make build-cross GOOS=windows GOARCH=amd64 OUTPUT=songloft.exeProduction builds automatically detect UPX and compress. At compile time, Version, GitCommit, BuildTime, and BuildType are injected into the internal/version package via -ldflags.
7.4 Docker Build
Multi-stage build: golang:1.26-alpine (build stage, accelerated by BuildKit cache mounts) → alpine:latest (runtime, with ffmpeg, ALSA, ca-certificates).
make docker-build # Test image
docker build --build-arg VERSION=2.10.0 -t songloft . # Full version
docker build --build-arg LITE_BUILD=true -t songloft:lite . # Lite version7.5 Frontend Embedding Path
When building the full version, the frontend artifact must be located at songloft-player-build/web-embedded/ (not songloft-player/build/), embedded by internal/app/embed.go.
Section sources
- AGENTS.md:178-185 -- Build tags and matrix
- Makefile:1-4 --
CGO_ENABLED=0 - Makefile:19-26 -- LDFLAGS injection
- Dockerfile:1-57 -- Multi-stage build flow
8. Git Commit Conventions
8.1 Commit Format
Follows Conventional Commits: type(scope): description
Common types: feat (new feature), fix (bug fix), refactor (refactoring), docs (documentation), test (testing), chore (build/config), perf (performance optimization).
feat(cache): add custom cache directory support
fix(fingerprint): remove invalid length threshold in ExtractFingerprint8.2 Prohibitions
- Commit messages must not add a
Co-Authored-Bytrailer
8.3 Submodules Referencing Parent Repository Issues
When a submodule commit references a parent repository issue, the full path must be used:
fix(player): handle CORS error, see songloft-org/songloft#155 # Correct
fix(player): handle CORS error, see #155 # Wrong (resolves to a submodule issue)8.4 Version Release
make bump # patch (2.10.0 → 2.10.1)
make bump TYPE=minor # minor (2.10.0 → 2.11.0)
make bump TYPE=major # major (2.10.0 → 3.0.0)After pushing the tag, .github/workflows/release.yml automatically builds and releases.
Section sources
- AGENTS.md:169-173 -- Git commit conventions
- Makefile:339-341 --
make bumptarget
