This document defines the coding standards and conventions for the backend codebase.

Table of Contents

  1. Naming Conventions
  2. Database Conventions (PostgreSQL/GORM)
  3. API Conventions
  4. Architecture (Hexagonal)
  5. Go Code Conventions
  6. File Organization
  7. Error Handling
  8. Migration Guide
  9. Why These Conventions?

Naming Conventions

General Rules

[!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 use snake_case for all database fields.

✅ Correct GORM Model Definition:

Standard Field Names

Use these consistent field names across all tables:

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 users and organizations tables.

Table Naming

  • Format: snake_case and plural (e.g., leave_requests, bulk_jobs).
  • Better Auth Tables: users, organizations, members, invitation.

API Conventions

Request/Response Format

Always use snake_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 to offset)
  • Params: limit, page (for offset mode), cursor (for cursor mode).

Response Format:

[!NOTE] Use the pagination.Wrap(items, meta) helper from backend/go/pkg/pagination.

Status Enums

Use UPPERCASE for status values:
Why: Clear distinction from regular string values, widely used convention.

Architecture

We follow a Hexagonal Architecture (Ports and Adapters) pattern to keep business logic decoupled from infrastructure.

Layers

  1. Domain (internal/modules/{module}/domain/entity):
    • Pure Go structs representing business entities.
    • Minimal dependencies (no GORM or JSON tags here if possible).
  2. Application (internal/modules/{module}/application):
    • Ports: Interfaces for repositories and external services.
    • Usecases: Orchestration of domain logic.
  3. 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 pass context.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:

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:

  1. Be descriptive - file names should clearly indicate their purpose.
  2. Suffix grouping - group related files by suffix (e.g., _test.go, _repository.go).
  3. 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:
  1. Use git mv to preserve history.
  2. Update all imports and run go build to verify.
  3. 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:
  1. Schema: Define a GORM model with UUID primary keys.
  2. Repository: Implement a GORM-based repository in adapter/outbound/persistence/postgresql.
  3. Pagination: Switch to the unified PaginatedResponse envelope.
  4. Auth: Use the shared users and organizations tables (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-lint for Go code.
  • go fmt and go vet.

Code Review Checklist

  • Database fields use snake_case.
  • API fields use snake_case.
  • Primary keys use UUID v4.
  • Business logic resides in domain or usecase.
  • Errors are wrapped with %w.

Last Updated: 2026-05-01
Version: 2.0
Maintained By: Backend Team