Skip to content

Domain Setup

How to connect a new domain to CoreAPI so it receives HTTP requests and has access to the database.


What connecting a domain means

Three things need to happen:

  1. Your domain exposes a Setup() function in routes/wire.go
  2. main.go calls that Setup() and passes in a database connection
  3. Your domain registers its models in main.go before db.Init()

That's the entire contract between your domain and the rest of CoreAPI.

Older domains in the codebase (spatial, catalogue, provisioning) use a flat pattern where routes.go lives directly in the domain root and registers routes via RegisterXxxRoutes(v1) — no Setup(), no wire.go, no injected database. That pattern is still running but not what we build with. See Flat pattern at the bottom of this page if you're reading one of those domains.


The files involved

On your domain's side, two files:

domain/your-domain/
└── routes/
    ├── wire.go     ← Setup() — the only function main.go calls
    └── routes.go   ← registerRoutes() — internal, maps URLs to handlers

On CoreAPI's side, one file: main.go.


Step 1 — routes/wire.go

Setup() is the entry point. It reads config, wires the handler, and calls registerRoutes. main.go never calls NewHandler directly — that's wire's job.

package routes

import (
    "github.com/gofiber/fiber/v2"
    "gorm.io/gorm"
    yourdomain "mashup.castis.io/playtelly/CoreAPI/domain/your-domain"
    "mashup.castis.io/playtelly/CoreAPI/domain/your-domain/handlers"
)

func Setup(r fiber.Router, db *gorm.DB) {
    cfg := yourdomain.ConfigFromEnv()
    h   := handlers.NewHandler(db, cfg)
    registerRoutes(r, h)
}

If your domain needs extra dependencies (storage client, JWT service), add them as parameters here. See domain/media/routes/wire.go for an example with storage.


Step 2 — routes/routes.go

Maps URL paths to handler methods. Not exported — main.go never calls this directly.

package routes

import (
    "github.com/gofiber/fiber/v2"
    "mashup.castis.io/playtelly/CoreAPI/domain/your-domain/handlers"
)

func registerRoutes(r fiber.Router, h *handlers.Handler) {
    g := r.Group("/your-domain")
    g.Get("/",     h.List)
    g.Get("/:id",  h.GetByID)
    g.Post("/",    h.Create)
    g.Put("/:id",  h.Update)
    g.Delete("/:id", h.Delete)
}

Step 3 — main.go

Two additions — register your models before db.Init(), call Setup after:

import (
    yourdomainmodels "mashup.castis.io/playtelly/CoreAPI/domain/your-domain"
    yourdomainroutes  "mashup.castis.io/playtelly/CoreAPI/domain/your-domain/routes"
)

// --- Infrastructure ---
db.RegisterModels(
    // existing models...
    &yourdomainmodels.YourModel{}, // ← add here
)

db.Init()
db.RunMigrationMain()

// --- Routes ---
yourdomainroutes.Setup(v1, db.GetDB()) // ← add here

Two import aliases because the root package is used for RegisterModels and the routes subpackage is used for Setup.

Pass db.GetDB() for CoreAPI tables, db.GetZooDB() for ticketing tables. See Database for when to use which.


The full flow

main.go
  ├── db.RegisterModels(&yourdomainmodels.YourModel{})
  ├── db.Init()
  ├── db.RunMigrationMain()
  └── yourdomainroutes.Setup(v1, db.GetDB())
                              │
                         wire.go Setup(r, db)
                              │
                         handlers.NewHandler(db, cfg)
                              │
                         h.db — used in all handlers

The domain never calls db.GetDB() itself. It receives the connection and stores it. This is what lets you swap databases from main.go without touching the domain.


Reference implementation

domain/media and domain/distribution are the cleanest examples of this pattern in the codebase. When in doubt, look there. For recommended internal structure — handler files, repositories, config — see Patterns.


Flat pattern (previous)

Older domains like spatial, catalogue, and provisioning connect differently — a single routes.go in the domain root, no wire.go, no injected database. They call db.GetDB() internally and register via a named function:

// domain/spatial/routes.go

func RegisterLocationRoutes(v1 fiber.Router) {
    db := db.GetDB() // ← fetches db internally, not injected
    v1.Get("/locations", func(c *fiber.Ctx) error {
        // handler inline or calls a flat function
    })
}
// main.go
spatial.RegisterLocationRoutes(v1)
spatial.RegisterVenueRoutes(v1)

This works but has two drawbacks — the domain is coupled to pkg/db directly, and you can't swap databases without changing the domain itself. The wire.go + Setup() pattern solves both. These domains are being migrated over time.