Skip to content

Reference App

reference/standard-service is the canonical application shape in Plumego. Use it when the question is file layout, route ownership, and what is safe to copy into a real service.

If you have not run anything yet, start with Getting Started. If the question is ownership rather than service shape, use Repository Boundaries.

reference/standard-service/
main.go
internal/
app/
app.go
routes.go
config/
config.go
handler/
api.go
health.go
FileDemonstratesCopy this idea
main.goLoad config, build app, register routes, start serverKeep bootstrap order explicit
internal/app/app.goApplication-local constructionWire dependencies through constructors
internal/app/routes.goRoute registrationKeep one method, one path, one handler visible
internal/config/config.goService-local configKeep config parsing near the app, not in stable roots
internal/handler/api.goJSON API handlersUse net/http signatures and contract write paths
internal/handler/health.goLiveness/readiness handlersKeep health endpoints small and transport-only
1

Bootstrap

load config, build app, register routes

2

Construct

core.New plus app-local dependencies

3

Configure

service-local config parsing

4

Handle

net/http handlers and contract writes

5

Do not infer

ownership lives in docs/specs/tasks

main.go
-> config.Load()
-> app.New(cfg)
-> core.New(cfg.Core, dependencies)
-> local handlers and dependencies
-> RegisterRoutes()
-> /healthz
-> /readyz
-> /api/v1/greet
-> Start()
func main() {
cfg := config.Load() // env → struct, explicit, no global
app := app.New(cfg) // wire deps through constructors
app.RegisterRoutes() // all routes in one place
app.Start() // Prepare → ListenAndServe with signal handling
}
type App struct {
Core *core.App
Handler *handler.APIHandler
}
func New(cfg *config.Config) *App {
logger := plog.NewLogger()
coreApp := core.New(core.DefaultConfig(), core.AppDependencies{Logger: logger})
return &App{
Core: coreApp,
Handler: handler.New(logger),
}
}

internal/app/routes.go — one method, one path, one handler

Section titled “internal/app/routes.go — one method, one path, one handler”
func (a *App) RegisterRoutes() {
a.Core.Get("/healthz", http.HandlerFunc(a.Handler.Health.Live))
a.Core.Get("/readyz", http.HandlerFunc(a.Handler.Health.Ready))
a.Core.Get("/api/v1/greet", http.HandlerFunc(a.Handler.API.Greet))
}

internal/handler/api.go — stdlib-compatible handler

Section titled “internal/handler/api.go — stdlib-compatible handler”
type APIHandler struct{ Logger log.Logger }
func (h APIHandler) Greet(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
_ = contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeRequired).Detail("field", "name").Build())
return
}
_ = contract.WriteResponse(w, r, http.StatusOK,
map[string]string{"message": "hello, " + name}, nil)
}

Safe to copyDo not infer
Explicit bootstrap orderEvery application must have identical internal packages
App-local constructors and route registrationreference owns repository-wide boundaries
Standard-library handler signaturex/* packages are part of the default path
contract.WriteResponse and contract.WriteErrorFeature-specific response helper families are not encouraged
Stable-root-only canonical pathChecked-in experimental extensions carry the same compatibility promise
SituationUse this page?
Starting a new Plumego serviceYes
Validating handler and route shapeYes
Comparing service skeletonsYes
Deciding which package owns a capabilityNo, use Modules Overview
Deciding compatibility or adoption postureNo, use Release Posture
Next questionPage
How does a request flow after route registration?Request Flow
Which surface owns a change?Repository Boundaries
Which module should I open?Modules Overview
Which package is safe to adopt?Release Posture