Code2b DocsLocal preview

Naming Conventions

Standards every Code2b engineer follows when writing backend code, database schema, and HTTP APIs. When in doubt, match what enrichabl already does.


General principles

  1. Be explicit over clever. Names should read like documentation.
  2. One concept, one name. Do not alias the same entity as user, profile, and account in different layers unless they are genuinely different domain objects.
  3. Match the layer. Database uses snake_case. Go exported identifiers use PascalCase. JSON field names follow the consumer (usually snake_case for REST).
  4. Prefix by entity in the database. Every column on a table is prefixed with that table's entity name. This avoids ambiguous status or created_at in joins.
  5. Verify before you ship. Run go build ./..., go vet, and gofmt on Go changes. Migrations must be idempotent and wrapped in a transaction.

Database

Tables

Rule Example
Plural nouns users, orders, enrichment_jobs
snake_case, lowercase credit_wallets, api_keys
Junction / child tables name both sides enrichment_job_leads, pipeline_leads
CREATE TABLE IF NOT EXISTS enrichment_jobs (
    enrichment_job_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    enrichment_job_user_id    UUID NOT NULL REFERENCES profiles(profile_id),
    enrichment_job_status     TEXT NOT NULL DEFAULT 'PENDING',
    enrichment_job_created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    enrichment_job_updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

Columns

Pattern Example
Primary key {entity}_id
Foreign key {entity}_{referenced_entity}_id
Timestamps {entity}_created_at, {entity}_updated_at
Status / type enums {entity}_status, {entity}_type
Booleans {entity}_is_active, {entity}_has_email
Counts / amounts {entity}_credit_balance, {entity}_retry_count

Child tables keep the full prefix: enrichment_job_lead_status, not bare status.

Enums and status values

Use SCREAMING_SNAKE_CASE string constants in the database and in Go:

const (
    EnrichmentStatusPending    = "PENDING"
    EnrichmentStatusInProgress = "IN_PROGRESS"
    EnrichmentStatusCompleted  = "COMPLETED"
    EnrichmentStatusFailed     = "FAILED"
)

Never store display labels in the DB. Store the machine value; format for humans in the UI.

Indexes and constraints

Type Pattern Example
Index idx_{table}_{columns} idx_enrichment_jobs_user_id
Unique uq_{table}_{columns} uq_api_keys_key_hash
Check chk_{table}_{rule} chk_service_pricing_unit_check
Foreign key {table}_{column}_fkey Postgres default is fine

Migrations

  • File name: migration-X.Y.Z.sql or numbered 001_create_users.sql for greenfield services.
  • Always wrap in BEGIN; ... COMMIT;.
  • Use idempotent DDL: CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS.
  • Bump the project version table when the repo uses one.
  • Update the consolidated schema.sql after every migration.
  • Apply by hand with psql. We do not rely on auto-migration runners against production.

Go code

Packages and directories

Follow the DDD + hexagonal layout documented in Go: DDD + Hexagonal Architecture.

Layer Package role Example path
Entry main only wires dependencies cmd/api/main.go
Inbound HTTP/gRPC handlers, middleware internal/inbound/http/handler/
Domain Models, services, repository ports internal/domain/user/
Outbound DB, cache, external APIs, adapters internal/outbound/repository/

Package names: lowercase, single word, no underscores. Directory names may use underscores when the domain is multi-word (ai_column_template is acceptable; prefer one word when possible).

Files

Rule Example
One primary exported function per file in handler/usecase layers startEnrichment.go, getJobByID.go
File name matches the main symbol, camelCase user_handler.go, personalTokenAuth.go
Tests sit next to source service_test.go
No utils.go junk drawers Split by domain or delete

Functions and methods

Visibility Style Example
Exported PascalCase verb phrase StartEnrichment, GetUserByID, ValidateSubmission
Unexported camelCase verb phrase preflightCheck, mapRowToLead
Constructors New or NewThing NewPostgresRepository, NewHandler
Interfaces (ports) Noun or -er suffix UserRepository, EmailSender
Errors Err + PascalCase ErrNotFound, ErrPipelineAccessDenied

Receivers: short, typed, consistent within a file (s *Service, h *Handler, r *Repository).

Structs and types

// Domain model: mirrors DB where persisted
type EnrichmentJob struct {
    ID        uuid.UUID `json:"enrichment_job_id"`
    UserID    uuid.UUID `json:"enrichment_job_user_id"`
    Status    string    `json:"enrichment_job_status"`
    CreatedAt time.Time `json:"enrichment_job_created_at"`
}
 
// Request DTO: shorter JSON tags, no table prefix
type StartEnrichmentRequest struct {
    PipelineID     uuid.UUID   `json:"pipeline_id"`
    LeadIDs        []uuid.UUID `json:"lead_ids"`
    EnrichmentType string      `json:"enrichment_type"`
}

Persisted models mirror DB column names in JSON tags. Request/response DTOs use shorter, API-friendly names.

Context and errors

  • Pass context.Context as the first parameter on anything that does I/O.
  • Wrap errors with context: fmt.Errorf("get user %s: %w", id, err).
  • Return domain errors from services; translate to HTTP status in handlers only.
  • Do not log and return the same error unless adding value.

HTTP API routes

Path style

Rule Example
Prefix app routes with /api /api/enrichment/start
Public token API under /v1 /v1/leads, /v1/enrichment/jobs
kebab-case path segments /api/ai-columns/templates
Plural resource collections /api/pipelines, /api/users
Nested resources /api/pipelines/:id/leads/:lead_id
Health checks /health or /healthz + /readyz

Route parameters

Use :id for generic IDs. Use descriptive names when multiple IDs appear in one path: :job_id, :lead_id.

Handler naming

Register routes in one place (router.go). Handler methods match the action:

func (h *EnrichmentHandler) Start(c *fiber.Ctx) error { ... }
func (h *EnrichmentHandler) GetJob(c *fiber.Ctx) error { ... }
func (h *EnrichmentHandler) ListJobs(c *fiber.Ctx) error { ... }

Response shape

Use consistent JSON envelopes where the project already does:

{
  "data": { ... },
  "error": null
}

or plain resource objects for simple services. Do not mix styles within one service.


Frontend (when applicable)

Item Convention
React components PascalCase file and export: UserTable.tsx
Hooks use prefix: useEnrichmentJobs.ts
Route segments (App Router) kebab-case folders: app/(dashboard)/enrichment-jobs/
CSS Tailwind utilities; shared tokens in design system
API client functions camelCase: fetchEnrichmentJobs, createPipeline

Git and PR hygiene

Rule Detail
Branch names feat/short-description, fix/..., chore/...
One ticket per branch Never stack unrelated work
Commits Imperative subject, one logical change each
No AI attribution No co-author trailers or generated-by footers
Never commit secrets .env, keys, tokens stay local

Checklist for new code

Before opening a PR:

  • Table and column names follow entity-prefix snake_case
  • Go exports are PascalCase; files are camelCase
  • Repository interface lives in domain/; implementation in outbound/
  • Handlers do not import SQL drivers or call raw SQL
  • Migration is transactional and idempotent
  • go build ./... and go vet ./... pass
  • API paths match existing service style (/api/... vs bare REST)

When unsure, open enrichabl's backend/database/schema.sql or services/enrichment-service/ and copy the pattern.