Contributing
Laju Go is open source. Contributions are welcome — bug fixes, features, docs, and improvements to the boilerplate itself. This guide covers the rules that keep the codebase consistent.
git clone https://github.com/maulanashalihin/laju-go.gitcd laju-gomake setupmake setup copies .env.example to .env, runs go mod tidy, go mod download, and npm install.
Architecture Rules
Section titled “Architecture Rules”Before writing code, read the Architecture Overview and the Three-Tier Rule. The short version:
Handler → Service → Query → DBNo layer may skip another. Handlers parse requests and return responses. Services hold business logic. Queries are the only layer that executes SQL. Models are pure data structs.
Handler Structure
Section titled “Handler Structure”Each module gets its own handler file. Do not merge all routes into one giant handler:
- ✅
app/handlers/auth.go— login, register, OAuth - ✅
app/handlers/upload.go— file uploads - ❌
app/handlers/handler.go— 1000+ lines, every route
Models
Section titled “Models”app/models/ contains pure data structs — domain entities and DTOs. No business logic, no DB access, no imports of app/queries.
Code Generation
Section titled “Code Generation”Laju Go uses two code generators. Both produce Go code that you must never edit by hand.
Generates type-safe Go query functions from SQL.
make db-generate# orsqlc generate- Input:
queries/*.sql+ schema frommigrations/ - Output:
app/queries/(typed Go functions) - Never edit
app/queries/manually. Change the SQL inqueries/*.sqland regenerate.
Generates type-safe Go rendering code from .templ templates.
make templ# ortempl generate- Input:
templates/*.templ - Output:
*_templ.go - Edit only
.templfiles.*_templ.gois overwritten bytempl generate. - Both
.templand*_templ.gomust be committed so the repo builds without running the generator.
Generate Both
Section titled “Generate Both”make generate # runs templ then db-generateMigration Rules
Section titled “Migration Rules”Goose migrations live in migrations/. The rules are strict:
Never Edit a Deployed Migration
Section titled “Never Edit a Deployed Migration”🔴 Never edit a deployed migration. Create a new migration file instead.
Goose tracks applied migrations by filename. Once a migration runs in production, Goose records its version and skips it on future startups. Editing an old migration file has no effect in production but can break local development databases.
If you need to change a table’s schema, write a new migration that alters it.
One Table Per File
Section titled “One Table Per File”Each migration file creates or alters exactly one table. This keeps migrations reviewable and rollback-safe.
# Generate a new migrationgo run github.com/pressly/goose/v3/cmd/goose@latest -dir migrations create add_users_table sqlRun Migrations
Section titled “Run Migrations”Migrations run automatically on app startup. To run them manually:
make migrate# orgo run github.com/pressly/goose/v3/cmd/goose@latest -dir migrations sqlite3 ./data/app.db upAlways use
go runto invoke Goose, not a standalonegoosebinary. This ensures the correct version is used.
Build Order
Section titled “Build Order”When building for production:
make build # vite build → go buildvite build must run before go build. The Go binary reads dist/.vite/manifest.json to resolve hashed frontend asset filenames. Building Go first produces a binary that cannot locate assets.
Testing
Section titled “Testing”All contributions should pass existing tests:
go test ./...Tests use in-memory SQLite — no mocks, no external services. See Testing for the full strategy.
When adding a new service method or query, add a test that exercises it against a real in-memory database.
Keeping Docs in Sync
Section titled “Keeping Docs in Sync”If you change behavior, update the docs. The docs live in two places:
site/src/content/docs/— the Starlight documentation site (this site).llm-wiki/wiki/— internal wiki for AI agents
When you add a feature, update or create the relevant doc page. When you change an existing pattern, update the page that documents it.
Commit Checklist
Section titled “Commit Checklist”Before opening a PR:
-
go test ./...passes -
make generaterun if you changedqueries/*.sqlortemplates/*.templ -
go mod tidyrun if you changed dependencies (commit updatedgo.sum) - No manual edits to
app/queries/or*_templ.go - New migrations follow the one-table-per-file rule
- No edits to already-deployed migrations
- Docs updated if behavior changed
Linting
Section titled “Linting”make lint# orgolangci-lint run ./...The config is in .golangci.yml. Pre-commit hooks (.pre-commit-config.yaml) run linting automatically if you have pre-commit installed.
Installer Strip List
Section titled “Installer Strip List”If you are contributing to the boilerplate itself (not an app built on it), note that npm create laju-go@latest strips dev-only files when scaffolding a new project:
create-laju-go/,.llm-wiki/sources,.devin,AGENTS.md,site/- It keeps
.llm-wiki/conceptsand.llm-wiki/entities(useful for understanding the repo)
If you add a file that should not be included in new projects, add it to the installer’s strip list.