-
Notifications
You must be signed in to change notification settings - Fork 0
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.
- Prerequisites
- Install the library
- Minimal
main.go - Run the bundled example
- Try it end-to-end with
curl - Next steps
-
Go 1.23+ —
go version -
Docker + Docker Compose — used to run the Postgres dev database. Any local Postgres works too; just point
DATABASE_URLat it. -
make(optional but recommended) — the repo ships aMakefilewith all-in-one targets likemake example.
You do not need to install Postgres on the host; the example uses a containerised Postgres 16.
For a new project, pull in core and the GORM adapter:
go get github.com/dnahilman/goten
go get github.com/dnahilman/goten/adapters/gormIf you want username-based login as well, also pull the username plugin:
go get github.com/dnahilman/goten/plugins/usernameEach module is independently versioned — go get only what you actually use.
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:
-
Secretmust be ≥ 32 bytes. Goten refuses to start with a shorter key. Use a real random value in production. -
BaseURLmust 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.
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.
From the repo root:
make exampleThis builds the CLI, starts Postgres, applies migrations, and runs the example server on :8080.
# 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 |
| 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.
With the server running on :8080, walk through the full flow.
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..."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.
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": {...} }.
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.
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"}'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"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.
-
Add more login methods — see Plugins for the Username plugin and instructions on building your own (
Endpoints,SchemaProvider,MigrationProvider, hook providers). -
Manage migrations —
goten migrate status / up / down / generate <name>. See the CLI section in the README. -
Harden for production — set
TrustedOriginsto 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.