Skip to content

Quick Start

dnahilman edited this page May 26, 2026 · 9 revisions

Quick Start

This guide walks you from zero to a working Goten-backed HTTP server in a brand-new Go project. By the end you will have:

  • A configured goten.config.yaml + .env
  • Core auth schema migrated to a real PostgreSQL database
  • A main.go exposing sign-up / sign-in / protected routes
  • A verified end-to-end flow using curl

If you just want a runnable reference instead of building from scratch, see examples/basic/ in the repo. The footer of this page also links back there as a safety net.

Contents

  1. Prerequisites
  2. Initialize the project
  3. Install the CLI
  4. Configure: goten.config.yaml + .env
  5. Scaffold migrations
  6. Apply migrations
  7. Write main.go
  8. Run and test with curl
  9. Adding a plugin later
  10. Next steps

1. Prerequisites

  • Go 1.23+go version.
  • A PostgreSQL database — any reachable Postgres works (local, managed, RDS, Neon, Supabase, …).

No Postgres locally? One-shot Docker container:

docker run -d --name pg \
  -e POSTGRES_USER=goten -e POSTGRES_PASSWORD=goten -e POSTGRES_DB=goten \
  -p 5432:5432 postgres:16-alpine

The DSN for this is postgres://goten:goten@localhost:5432/goten?sslmode=disable.


2. Initialize the project

mkdir myapp && cd myapp
go mod init github.com/yourname/myapp

# Core + GORM adapter
go get github.com/dnahilman/goten
go get github.com/dnahilman/goten/adapters/gorm

# Optional: username plugin (login by username instead of/alongside email)
go get github.com/dnahilman/goten/plugins/username

Each module is independently versioned — go get only what you actually use.


3. Install the CLI

go install github.com/dnahilman/goten/cmd/goten@latest

After this, goten should be on your PATH. Verify with goten --help.


4. Configure: goten.config.yaml + .env

Create goten.config.yaml at the project root:

# yaml-language-server: $schema=https://raw.githubusercontent.com/dnahilman/goten/main/goten.config.schema.json
env_file: .env
database:
  url: ${DATABASE_URL}
migrations:
  plugins:
    - username   # delete this line if you skipped the optional plugin

Editor autocomplete: the magic comment on line 1 makes VS Code (with the Red Hat YAML extension), JetBrains, and other LSP-aware editors give field autocomplete, inline docs on hover, and red squiggles on typos.

Create .env next to it:

DATABASE_URL=postgres://goten:goten@localhost:5432/goten?sslmode=disable

And add .env to .gitignore:

echo ".env" >> .gitignore

That's it for credentials. The CLI auto-loads .env from the project directory before resolving ${DATABASE_URL} in the YAML, so no export is needed and no secret lives in version control.


5. Scaffold migrations

goten init

This copies the core auth migrations (and any plugins listed in §4) into your project:

.
├── goten.config.yaml
├── .env
├── migrations/                                # core auth: users, sessions, accounts
│   ├── 20260520120000_initial.up.sql
│   └── 20260520120000_initial.down.sql
└── plugins/
    └── username/
        └── migrations/                        # only if you listed `- username`
            ├── 20260520130000_add_username.up.sql
            └── 20260520130000_add_username.down.sql

goten init is idempotent: re-running it later (e.g. after adding another plugin) only writes new files. Existing files with identical content are skipped silently; files with different content require --force to overwrite.


6. Apply migrations

goten migrate up

Output:

Applying 2 migration(s)...
  → 20260520120000_initial [core] ... OK
  → 20260520130000_add_username [username] ... OK
✓ Done

You can re-check anytime with goten migrate status.


7. Write main.go

package main

import (
    "log"
    "net/http"

    goten "github.com/dnahilman/goten"
    gormadapter "github.com/dnahilman/goten/adapters/gorm"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(
        postgres.Open("postgres://goten:goten@localhost:5432/goten?sslmode=disable"),
        &gorm.Config{},
    )
    if err != nil {
        log.Fatal(err)
    }

    auth, err := goten.New(goten.Config{
        // BaseURL MUST match the URL clients use to reach the server.
        // Mismatches break cookie scoping and the CSRF origin check.
        BaseURL: "http://localhost:8080",

        // Secret MUST be >= 32 bytes. Generate one with: openssl rand -base64 32
        Secret: "your-32-byte-secret-key-here!!!!",

        Adapter: gormadapter.New(db),
    })
    if err != nil {
        log.Fatal(err)
    }

    http.Handle("/api/auth/", auth.Handler())

    http.Handle("/api/me", auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user, ok := goten.UserFromContext(r.Context())
        if !ok {
            // RequireAuth already short-circuits unauthenticated requests,
            // so this branch is mostly a guard for future refactors.
            http.Error(w, "no user", http.StatusInternalServerError)
            return
        }
        _ = user // user.ID, user.Email, user.Name ...
    })))

    log.Fatal(http.ListenAndServe(":8080", nil))
}

Two things that catch most people:

  • Secret must be ≥ 32 bytes. Goten refuses to start with a shorter key. Use a real random value in production.
  • BaseURL must match the URL clients reach. A mismatch breaks cookie scoping and the CSRF origin check.

Using Gin instead of net/http? Same setup — see Goten with Gin for the gin.WrapH mounting pattern and a Gin-idiomatic middleware adapter.

Run it:

go run .

8. Run and test with curl

With the server on :8080, walk through the full flow.

Sign up

curl -i -X POST http://localhost:8080/api/auth/sign-up/email \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"correct-horse-battery-staple","name":"Alice"}'

Response includes a session token (in Set-Cookie and in the JSON body as token, prefixed g10_). Save it:

TOKEN="g10_...paste from response..."

Sign in

curl -i -X POST http://localhost:8080/api/auth/sign-in/email \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"correct-horse-battery-staple"}'

Get current session

Cookie:

curl -i http://localhost:8080/api/auth/get-session \
  -H "Cookie: goten.session_token=$TOKEN"

Bearer:

curl -i http://localhost:8080/api/auth/get-session \
  -H "Authorization: Bearer $TOKEN"

Protected route

curl -i http://localhost:8080/api/me \
  -H "Authorization: Bearer $TOKEN"

200 OK when the token is valid; 401 Unauthorized otherwise.

Sign out

curl -i -X POST http://localhost:8080/api/auth/sign-out \
  -H "Authorization: Bearer $TOKEN"

Username plugin (if enabled in §4)

curl -i -X POST http://localhost:8080/api/auth/sign-up/username \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"correct-horse-battery-staple","email":"alice2@example.com"}'

curl -i -X POST http://localhost:8080/api/auth/sign-in/username \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"correct-horse-battery-staple"}'

Session management

# List
curl -i http://localhost:8080/api/auth/list-sessions \
  -H "Authorization: Bearer $TOKEN"

# Revoke one
curl -i -X POST http://localhost:8080/api/auth/revoke-session \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"session_id":"g10_018f..."}'

# Revoke all other sessions
curl -i -X POST http://localhost:8080/api/auth/revoke-other-sessions \
  -H "Authorization: Bearer $TOKEN"

9. Adding a plugin later

The same goten init command makes adding a plugin a 4-step affair:

# 1. Pull the plugin
go get github.com/dnahilman/goten/plugins/<plugin>

# 2. Add it to goten.config.yaml under migrations.plugins:
#      plugins:
#        - <plugin>

# 3. Re-scaffold (existing files are skipped; new plugin files are written)
goten init

# 4. Apply the new migrations
goten migrate up

Then register the plugin in your goten.New(...) call:

import usernameplugin "github.com/dnahilman/goten/plugins/username"

auth, _ := goten.New(goten.Config{
    // ...
    Plugins: []goten.Plugin{
        usernameplugin.New(usernameplugin.Options{}),
    },
})

Restart the app — the new endpoints (e.g. /api/auth/sign-up/username) are now live.


10. Next steps

  • More plugins / build your own — see Plugins in the README for the interfaces (Endpoints, SchemaProvider, MigrationProvider, hook providers).
  • Mount under Gin / Echo / Chi — see Goten with Gin; the pattern transfers to any framework that accepts http.Handler.
  • Manage migrationsgoten migrate status / up / down / generate <name>. See CLI.
  • Harden for production — set TrustedOrigins to your real domain(s) so non-bearer requests are origin-checked. See CSRF Protection.
  • Understand the module layout — Goten is split into core, adapters, plugins, and CLI as separate Go modules. See Architecture.

Still stuck or want a complete reference? examples/basic/ is a runnable end-to-end project (Postgres via Docker Compose, full Makefile) — clone the repo, make example, and you have a working app to compare against your own.

Clone this wiki locally