Getting Started

Introduction

What gpsystem is, who it's for, and what it leaves in your hands.

gpsystem is a service toolkit for building modular Go backends. It has two halves:

  • A library (github.com/gp-system/gpsystem): the service chassis, application lifecycle, an HTTP layer on Fiber v3 or chi, telemetry, an error model, validation, configuration, database access with context-carried transactions, background processing (queue, events, scheduler, worker), JWT auth, RBAC and a policy layer, object storage, e-mail, pagination.
  • A CLI (go tool gpsystem): generators that scaffold projects, modules, surfaces, handlers, events, listeners and jobs, including their TypeSpec contract files.

Why it exists

The Go ecosystem's strength is its small, independent, well-tested libraries. The tradeoff is that every project makes the same ten decisions from scratch: which HTTP engine, which logger, what graceful shutdown looks like, how you model errors. gpsystem does that work once, for everyone: it pulls the Go ecosystem's established libraries together into a single, maintained composition, instead of writing its own in their place.

Coming from a PHP background? The Coming from Laravel and Coming from Symfony pages map the two worlds concept by concept.

The kit covers all the base functionality you meet on most sites:

Each is written and tested once in the kit; your projects upgrade by bumping a single version in go.mod.

What gpsystem is NOT

  • Not a framework. There is no IoC container, no mandatory Module interface, no kit-owned lifecycle hook that a bootstrap discovers and calls. You write main(); every dependency is wired by hand, in explicit code. The kit's one inversion is the process lifecycle (Run(ctx, cfg, register)): it signals, shuts down, flushes, and never touches business wiring. See Architecture for exactly where that boundary sits.
  • Not a platform. gpsystem doesn't own your infrastructure: no proprietary deploy model, no cloud, no runtime; fiber.App, chi.Router and the pgx pool remain yours.
  • Not an ORM. There is no Eloquent- or Doctrine-style simulation; the repository layer writes SQL (pgx) or uses a query builder (bun), with explicit queries.
  • Generated code is yours. What the CLI scaffolds, you edit like any other code, except for the narrow, deliberately regenerated zone (the TypeSpec→OpenAPI→oapi-codegen transport stubs under gen/), which you don't hand-edit but drive by changing the spec.

It does not hide your dependencies

The kit's principle: centralize, don't abstract. Access to external dependencies is concentrated in one kit package each, but their types flow freely through the public API:

  • the register function of fiberx.Run hands you a *fiber.App, that of chix.Run a chi.Router: your handlers use the native API;
  • dbx/pg returns pgx.Rows and pgxpool.Pool, dbx/bunx a bun.DB;
  • queue works with *asynq.Task, and the worker's escape hatches (WithAsynqConfig, WithMux) hand you raw asynq.

This is deliberate: upstream documentation and Stack Overflow answers stay valid for you, and there is no kit-specific wrapper API to learn. The kit departs from this in three places, each for a stated reason:

  • the storage.Store interface hides the AWS SDK, so your application code doesn't have to import the AWS SDK for a file upload;
  • sentryx wraps sentry-go: Sentry is switchable via env (SENTRY_DSN empty = fully off), and a binary that doesn't use it never links it;
  • the mail Mailer interface fronts go-mail and mjml-go, so tests can swap in an in-memory mailer.

The parts are replaceable and omittable: dbx/pg works without server (say, in a CLI tool), Sentry and OTel can be switched off via env, the engine is chosen per project, pgx/bun by import. The Architecture page shows package by package what depends on what.

The ~20-line main.go

A module's entry point is this (graceful shutdown, telemetry, error rendering and validation already wired):

func main() {
    cfg := envconf.MustLoad[config.Config]()
    ctx := context.Background()
    pool := pg.MustNewPool(ctx, cfg.DB)

    err := fiberx.Run(ctx, cfg.Server, func(app *fiber.App) error {
        api := app.Group("/api/v1")
        deps := shop.Dependencies{
            DB:         pg.NewDB(pool),
            Transactor: pg.NewTransactor(pool),
        }
        shop.RegisterApi(api, deps)
        shop.RegisterAdmin(api, deps)
        return nil
    }, fiberx.WithCloser("pgxpool", func(context.Context) error {
        pool.Close()
        return nil
    }))
    if err != nil {
        log.Fatal(err)
    }
}

This is the entry point of the shop sample application. The documentation's examples build on that project throughout.

What stays in your hands

  • The engine API stays native. On Fiber your handlers receive fiber.Ctx, on chi net/http; fiberx.Run / chix.Run wire middleware and lifecycle but do not wrap routing or request handling. Whatever the engine can do, your app can do.
  • Generated code is yours. The scaffold lands in your repo and you edit it like any other code. The generators never overwrite an existing file and only touch marked anchor points (// gpsystem:*). There is no runtime interpreting your project.
  • Thin by design. fiberx.Run / chix.Run are a composition of signal.NotifyContext, listen, drain and an ordered cleanup list (the engine-agnostic app lifecycle), exactly the code you'd otherwise write per service, maintained in one place.

The shape of a consumer project

Every module gets its own entry point (cmd/<module>/main.go) and runs as its own process. Deploy them separately or together, as you like. The API contract is spec-first: TypeSpec compiles to OpenAPI 3.0, from which a strict, typed server interface is generated (for Fiber and chi alike). Your handler implements a generated interface, so drifting from the spec is a compile error.

Where to next

Copyright © 2026