Table of Contents
- Naming Conventions
- Database Conventions (PostgreSQL/GORM)
- API Conventions
- Architecture (Hexagonal)
- Go Code Conventions
- File Organization
- Error Handling
- Migration Guide
- Why These Conventions?
Naming Conventions
General Rules
| Context | Convention | Example |
|---|---|---|
| Database Fields | snake_case | user_id, created_at, is_active |
| API Request/Response | snake_case | leave_type_id, start_date, is_half_day |
| Go Struct Fields | PascalCase | UserID, CreatedAt, IsActive |
| Go Variables | camelCase | userId, createdAt, isActive |
| Go Functions | PascalCase (public), camelCase (private) | FindByID(), validateInput() |
| Go Package Names | lowercase (single word) | repository, services, domain |
| File Names | snake_case | leave_repository.go, handler_test.go |
[!NOTE] All file names MUST use snake_case. Be descriptive but avoid redundant prefixes if the directory already provides context.
Database Conventions
The backend uses PostgreSQL with GORM as the ORM.PostgreSQL Field Naming
Always usesnake_case for all database fields.
✅ Correct GORM Model Definition:
Standard Field Names
Use these consistent field names across all tables:| Field | Purpose | Type | Always Present |
|---|---|---|---|
id | Primary key | uuid | ✅ Yes |
created_at | Creation timestamp | timestamp | ✅ Yes |
updated_at | Last update timestamp | timestamp | ✅ Yes |
deleted_at | Soft delete timestamp | timestamp | Optional |
organization_id | Multi-tenancy org ID | uuid | ✅ Yes |
user_id | Associated user (Better Auth User ID) | text | Optional |
ID Conventions
Primary Keys (id)
- Format: UUID v4
- GORM Tag:
type:uuid;default:gen_random_uuid() - Why: Universally unique, natively supported by PostgreSQL, and allows ID generation in the application layer.
User IDs (user_id)
- Source: Better Auth
users.id - Type:
text(The Go backend treats this as a foreign reference to the Better Auth schema managed by the Bun monolith). - Note: The Go backend is read-only for the
usersandorganizationstables.
Table Naming
- Format:
snake_caseand plural (e.g.,leave_requests,bulk_jobs). - Better Auth Tables:
users,organizations,members,invitation.
API Conventions
Request/Response Format
Always usesnake_case for JSON fields in API requests and responses.
Pagination Standard
All list endpoints MUST use the standardized pagination envelope.Request Headers/Params:
- Header:
X-Pagination-Type: cursor(optional, defaults tooffset) - Params:
limit,page(for offset mode),cursor(for cursor mode).
Response Format:
[!NOTE] Use thepagination.Wrap(items, meta)helper frombackend/go/pkg/pagination.
Status Enums
Use UPPERCASE for status values:Architecture
We follow a Hexagonal Architecture (Ports and Adapters) pattern to keep business logic decoupled from infrastructure.Layers
- Domain (
internal/modules/{module}/domain/entity):- Pure Go structs representing business entities.
- Minimal dependencies (no GORM or JSON tags here if possible).
- Application (
internal/modules/{module}/application):- Ports: Interfaces for repositories and external services.
- Usecases: Orchestration of domain logic.
- Adapters (
internal/modules/{module}/adapter):- Inbound/HTTP: Echo handlers and request/response DTOs.
- Outbound/Persistence: GORM repository implementations.
- Outbound/Messaging: NATS event publishers.
Mapping Pattern
Always use mapping helpers to convert between Database Models (GORM) and Domain Entities.Go Code Conventions
Struct Definitions
Public structs use PascalCase, with JSON and GORM tags in snake_case:Repository Queries (GORM)
Always passcontext.Context and use method chaining.
Variable Naming
Function Naming
File Organization
Module Structure
Directory-Specific Naming
To ensure consistency across hexagonal layers, follow these directory-specific patterns:| Layer / Directory | Pattern | Example |
|---|---|---|
domain/entity/ | {entity}.go | leave.go, user.go |
application/port/ | {type}.go | leave_repository.go |
application/usecase/ | {type}_service.go | leave_query_service.go |
adapter/inbound/http/ | [type]_handler.go | handler.go, approval_handler.go |
adapter/inbound/event/ | [type]_consumer.go | consumer.go |
adapter/outbound/persistence/ | [type]_repository.go | leave_type_repository.go |
| Test Files | {filename}_test.go | leave_service_test.go |
Detailed Examples:
- Ports (
application/port/):leave_service.go(Interface),leave_repository.go(Interface). - Use Cases (
application/usecase/):leave_service.go(Command impl),leave_query_service.go(Query impl). - HTTP Adapters (
adapter/inbound/http/):handler.go(Primary),approval_handler.go(Specific). - Persistence (
adapter/outbound/persistence/):leave_request_repository.go(GORM impl).
Rules of Thumb:
- Be descriptive - file names should clearly indicate their purpose.
- Suffix grouping - group related files by suffix (e.g.,
_test.go,_repository.go). - Avoid redundant prefixes - if the directory name already provides context (e.g.,
.../http/handler.go), keep the file name simple. Use specific names (e.g.,approval_handler.go) only when multiple handlers exist in the same directory.
Benefits of This Structure
- Consistency: Easy to navigate the codebase across different modules.
- Predictability: Developers know exactly where to find handlers or repositories.
- Scalability: The pattern works consistently as the number of modules grows.
Naming Migration
When renaming files to follow these conventions:- Use
git mvto preserve history. - Update all imports and run
go buildto verify. - Run tests and update related documentation.
Error Handling
API Error Responses
Standardized error format:Go Error Wrapping
Always wrap errors with context using%w.
Migration Guide
If you find legacy code using MongoDB or Clerk patterns:- Schema: Define a GORM model with UUID primary keys.
- Repository: Implement a GORM-based repository in
adapter/outbound/persistence/postgresql. - Pagination: Switch to the unified
PaginatedResponseenvelope. - Auth: Use the shared
usersandorganizationstables (Read-Only).
Why These Conventions?
UUIDs for Primary Keys
✅ Security: Prevents ID enumeration. ✅ Distributed: Safe for multi-region sync without collisions.Hexagonal Architecture
✅ Testability: Business logic is decoupled from infrastructure and can be tested in isolation. ✅ Consistency: Uniform structure across all modules makes the codebase easier to navigate.Standardized Pagination
✅ Frontend Integration: The SolidStart frontend uses a single component to handle all paginated data.References
Enforcement
Pre-commit Hooks
golangci-lintfor Go code.go fmtandgo vet.
Code Review Checklist
- Database fields use
snake_case. - API fields use
snake_case. - Primary keys use UUID v4.
- Business logic resides in
domainorusecase. - Errors are wrapped with
%w.
Last Updated: 2026-05-01
Version: 2.0
Maintained By: Backend Team