Backend development in Announcable. Use when creating, modifying, or debugging backend code (Go, Chi router, GORM, templates, CSS/JS).
All commands run from backend/:
cd backend
Read the relevant documentation for what you're changing:
# Domain module
cat internal/domain/[module]/SUMMARY.md
# Handler architecture
cat internal/handler/SUMMARY.md
# Domain conventions
cat internal/domain/SUMMARY.md
# CSS architecture
cat assets/css/SUMMARY.md
# JS architecture
cat assets/js/SUMMARY.md
Run after every change. Do NOT trust your own assessment — verify through observable behavior.
# Go code changes
go build -o ./tmp/main . # Must compile
go vet ./... # No issues
# CSS/JS changes
npm run build # Vite must succeed
# Full check (if dev environment is running)
# Backend should respond at configured PORT
backend/
├── main.go # Entry point, routes, server setup
├── config/ # Environment configuration
├── internal/
│ ├── domain/ # Business logic (model, repository, service per module)
│ ├── handler/
│ │ ├── pages/ # HTML-serving handlers
│ │ ├── api/ # JSON API handlers
│ │ └── shared/ # Shared dependencies struct
│ ├── database/ # GORM setup, migrations, base model
│ ├── middleware/ # Auth, RBAC, rate limiting
│ ├── objstore/ # Minio wrapper
│ ├── email/ # Email sending
│ └── logger/ # Structured logging
├── templates/ # Go html/template (layouts, pages, partials)
├── assets/ # Source CSS/JS (Vite input)
│ ├── css/ # CSS source files
│ └── js/ # JS source files
└── static/ # Embedded assets (dist/ is Vite output)
Every domain module follows the same trio structure:
internal/domain/{module}/
├── SUMMARY.md # ← Read this first
├── model.go # GORM model (embeds database.BaseModel)
├── repository.go # Database access via GORM
├── service.go # Business logic, orchestration
└── common.go # Helper builders, shared types (optional)
Key conventions:
NewService(repo)NewRepository(db *database.DB)database.BaseModel provides UUID ID and timestampslogger.Get() for structured loggingrepo.db.StartTransaction() for multi-aggregate operationsPackage naming: Singular, lowercase (user, session, organisation — NOT users, sessions)
Handlers are organized by feature under pages/ (HTML) and api/ (JSON):
internal/handler/pages/{feature}/
├── page.go # Handlers struct, New(), GET handler, template
└── {domain}_{action}_action.go # One POST/PATCH/DELETE per file
Key conventions:
*Handlers struct receiving *shared.Dependencies{domain}_{action}_action.gotemplates.Construct() helpershared.BaseTemplateData for template dataAdding a new page:
pages/Handlers struct with New(deps) constructortemplates/pages/assets/css/pages/main.goTemplates use Go's html/template with composition:
root.html → layout (appframe/onboard/fullscreen) → page → partials
Blocks defined by root.html: layout-css, page-css, body, layout-js, page-js
var myTmpl = templates.Construct(
"my-page",
"layouts/root.html",
"layouts/appframe.html",
"pages/my-page.html",
)
// In handler:
myTmpl.ExecuteTemplate(w, "root", data)
Source files are in assets/, built by Vite to static/dist/:
# Development (watch mode)
npm run dev
# Production build
npm run build
CSS: Entry files in assets/css/layouts/ and assets/css/pages/ import from assets/css/components/ and assets/css/base/. All @import must come first.
JS: Vanilla JavaScript (no modules). Alpine.js components, HTMX handlers, global functions. External deps available via window.* from vendor bundle.
Never edit files in static/dist/ — they are auto-generated.
model.go, repository.go, service.go)announcable-migrations skill)page.go + action files)main.gogo build -o ./tmp/main . compilesgo vet ./... passesnpm run build succeeds (if CSS/JS changed)| Library | Purpose | Global |
|---|---|---|
| Alpine.js | Reactive UI framework | window.Alpine |
| HTMX | AJAX and partial page updates | window.htmx |
| SweetAlert | Modal dialogs | window.swal |
| Toastify | Toast notifications | window.Toastify |
| Feather Icons | Icon library | window.feather |
All bundled in /static/dist/vendor.js and /static/dist/vendor.css.
| Don't | Why | Instead |
|---|---|---|
Edit static/dist/ files | Auto-generated by Vite | Edit source in assets/ |
Call config.New() in hot paths | Creates new config each time | Cache at package level |
| Import across domain modules directly | Circular dependencies | Use IDs and defined structs |
| Use plural package names for domains | Convention conflict with handlers | Singular: user, session |
Put @import after CSS rules | CSS spec violation | All @import at top of file |
| Skip reading SUMMARY.md | Miss module conventions | Always read before modifying |
| Batch multiple logical changes | Hard to identify breakage | One change → validate |