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 with a real PostgreSQL database, migrations applied, and a verified sign-up / sign-in / protected-route flow.

If you just want to read code, jump straight to examples/basic/ in the repo — this guide drives that same example.

Contents

  1. Prerequisites
  2. Install the library
  3. Minimal main.go
  4. Run the bundled example
  5. Try it end-to-end with curl
  6. Next steps

1. Prerequisites

  • Go 1.23+go version
  • Docker + Docker Compose — used to run the Postgres dev database. Any local Postgres works too; just point DATABASE_URL at it.
  • make (optional but recommended) — the repo ships a Makefile with all-in-one targets like make example.

You do not need to install Postgres on the host; the example uses a containerised Postgres 16.


2. Install the library

For a new project, pull in core and the GORM adapter:

go get github.com/dnahilman/goten
go get github.com/dnahilman/goten/adapters/gorm

If you want username-based login as well, also pull the username plugin:

go get github.com/dnahilman/goten/plugins/username

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


3. Minimal main.go

The smallest useful integration looks like this:

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 where the server is actually reached by the browser/client.
        // It is used for cookie scoping and origin checks.
        BaseURL: "http://localhost:8080",

        // Secret must be at least 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)
    }

    // Mount Goten's auth endpoints at /api/auth/*
    http.Handle("/api/auth/", auth.Handler())

    // Protect your own endpoints
    http.Handle("/api/me", auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user, _ := goten.UserFromContext(r.Context())
        _ = 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 the client uses. A mismatch breaks cookie scoping and the CSRF origin check.

This snippet by itself is not enough — you still need a database with Goten's schema applied. The fastest way is to use the example below.


4. Run the bundled example

The repo ships a ready-to-run example at examples/basic/. It wires up the snippet above, adds the username plugin, and includes a Postgres docker-compose.yml.

Option A — all in one command

From the repo root:

make example

This builds the CLI, starts Postgres, applies migrations, and runs the example server on :8080.

Option B — step by step

# 1. Start Postgres (from examples/basic/)
cd examples/basic
docker compose up -d

# 2. Build the CLI (from the repo root)
cd ../..
make cli

# 3. Apply migrations (run from examples/basic/ so the CLI picks up goten.config.yaml)
cd examples/basic
../../bin/goten migrate up

# 4. Run the server
go run .

You should see listening on :8080. The server exposes:

Method Path Description
GET / Lists all routes
GET /health Health check
POST /api/auth/sign-up/email Register with email + password
POST /api/auth/sign-in/email Login with email + password
POST /api/auth/sign-up/username Register with username (plugin)
POST /api/auth/sign-in/username Login with username (plugin)
POST /api/auth/sign-out Revoke current session
GET /api/auth/get-session Current session + user
GET /api/auth/list-sessions All sessions for current user
POST /api/auth/revoke-session Revoke a specific session
GET /api/me Example protected route

Default credentials and ports

Setting Default
Postgres host:port localhost:5432
Postgres database goten
Postgres user / pass goten / goten
Server URL http://localhost:8080
GOTEN_SECRET dev-secret-32-bytes-min-please!! (dev only — override for anything real)

All of the above can be overridden with environment variables: DATABASE_URL, BASE_URL, GOTEN_SECRET, PORT.


5. Try it end-to-end with curl

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

Sign up with email

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"}'

You get back a JSON body with the new user and session, and a Set-Cookie header containing the session token. The token also appears in the response body as token (starts with g10_). Save it for later requests:

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

Sign in with email

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"}'

Same shape — fresh session, fresh cookie, fresh token.

Get the current session

Cookie-based (browser flow):

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

Bearer token (mobile / API flow):

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

Both return { "user": {...}, "session": {...} }.

Hit a protected route

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

200 OK when the token is valid, 401 Unauthorized when it's missing, expired, or revoked.

Username plugin

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 all sessions for the current user:

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

Revoke a specific session by ID:

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 (keep current):

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

Sign out

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

After this, $TOKEN no longer works on /api/me or /api/auth/get-session.


6. Next steps

  • Add more login methods — see Plugins for the Username plugin and instructions on building your own (Endpoints, SchemaProvider, MigrationProvider, hook providers).
  • Manage migrationsgoten migrate status / up / down / generate <name>. See the CLI section in the README.
  • 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.

If something doesn't work, double-check the two gotchas from step 3: the Secret length and the BaseURL matching the URL your client actually reaches.

Clone this wiki locally