Getting Started

Installation

Add ezz to a Go module and verify it serves a request.

Check your Go version

ezz uses method-aware routing patterns (GET /users/{id}) and Request.PathValue, both of which arrived in Go 1.22.

Terminal
go version

Configure private module access

sulv-io/ezz is a private repository. The public module proxy and checksum database cannot read it, so go get fails with a 410 Gone or 404 Not Found until you tell Go to bypass both and tell Git how to authenticate. This is a one-time setup per machine.

Mark the module as private

Terminal
go env -w GOPRIVATE='github.com/sulv-io/*'

GOPRIVATE is a convenience switch that sets GONOPROXY and GONOSUMDB to the same pattern. Go then fetches anything under github.com/sulv-io/ straight from the source and skips the sum.golang.org lookup that would otherwise fail on a repository it cannot see.

Let Git authenticate

Go always requests modules over HTTPS. Rewrite that to SSH so your existing key does the work:

Terminal
git config --global url."git@github.com:sulv-io/".insteadOf "https://github.com/sulv-io/"

If you use a personal access token instead of a key, rewrite to an authenticated HTTPS URL. The token needs repo scope:

Terminal
git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/sulv-io/".insteadOf "https://github.com/sulv-io/"

Verify

Terminal
go env GOPRIVATE
git ls-remote git@github.com:sulv-io/ezz.git HEAD

The first prints github.com/sulv-io/*. The second prints a commit hash. If it asks for a password or reports Repository not found, the credential is the problem, not Go.

The token form writes a credential in cleartext to ~/.gitconfig. Prefer SSH on workstations, and prefer a short-lived token injected as an environment variable in CI.

Add the module

Terminal
go get github.com/sulv-io/ezz

The core packages import only the standard library:

Import pathWhat it holds
github.com/sulv-io/ezzApp, routing, handlers, context, errors, middleware
github.com/sulv-io/ezz/bindingRequest binding and validation
github.com/sulv-io/ezz/middlewareGzip, rate limiting
github.com/sulv-io/ezz/openapiSpec generation and the Scalar UI
github.com/sulv-io/ezz/authJWT providers, auth rules, resolvers, GoTrue

One optional package pulls in a dependency. Add it only if you want Redis-backed rate limiting on the GoTrue proxy:

Terminal
go get github.com/sulv-io/ezz/auth/ratelimit

Pick a version

Use v1.1.0 or later.

Terminal
go get github.com/sulv-io/ezz@v1.1.0
v1.0.0 does not work with the import paths on this page. That tag predates the rewrite that moved the framework to the module root, so at v1.0.0 there is no package at github.com/sulv-io/ezz and the core packages live under core/ instead. go get appears to succeed and the import then fails with no required module provides package. If you see that error, check which version you resolved with go list -m github.com/sulv-io/ezz.

Set up CI

CI runners have neither your go env settings nor your ~/.gitconfig, so the same two pieces of configuration have to be applied in the workflow. With GitHub Actions:

.github/workflows/test.yml
- name: Configure private module access
  env:
    GITHUB_TOKEN: ${{ secrets.EZZ_READ_TOKEN }}
  run: |
    go env -w GOPRIVATE='github.com/sulv-io/*'
    git config --global \
      url."https://x-access-token:${GITHUB_TOKEN}@github.com/sulv-io/".insteadOf \
      "https://github.com/sulv-io/"

The default GITHUB_TOKEN is scoped to the repository running the workflow and cannot read a different private repository. Use a fine-grained PAT or a GitHub App token with read access to sulv-io/ezz, stored as a secret.

If your runner already has an SSH deploy key loaded, use the git@github.com: rewrite from the previous section instead and skip the token entirely.

Serve a request

Create a file and run it:

main.go
package main

import (
    "log"

    "github.com/sulv-io/ezz"
)

type PingResponse struct {
    Message string `json:"message"`
}

func main() {
    app := ezz.New()

    app.GET("/ping", ezz.HFunc(func(ctx *ezz.Context) (*PingResponse, error) {
        return &PingResponse{Message: "pong"}, nil
    }))

    log.Fatal(app.Listen(":8080"))
}
Terminal
go run main.go
Terminal
curl -i localhost:8080/ping

You should see a 200 with {"message":"pong"}, an X-Request-ID header added by the default middleware, and a request line in the server log.

No auth provider is registered in this example, so nothing is enforced. As soon as you call app.Auth(...), routes require a valid token unless you mark them .Public(). See Auth overview.

Next steps

  • Quick start builds a real API with auth, groups, and generated docs.
  • Routing covers patterns, groups, and route metadata.
Copyright © 2026