-
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 in a brand-new Go project. By the end you will have:
- A configured
goten.config.yaml - Generated GORM models for the core auth schema (and any plugins)
- A
main.gothat applies the schema withAutoMigrateand exposes 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.
- Prerequisites
- Initialize the project
- Install the CLI
- Configure:
goten.config.yaml - Generate the models
- Write
main.go - Run and test with
curl - Adding a plugin later
- Next steps
-
Go 1.25+ —
go version. (Goten's modules declarego 1.25.) - 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-alpineThe DSN for this is
postgres://goten:goten@localhost:5432/goten?sslmode=disable.
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/usernameEach module is independently versioned — go get only what you actually use.
go install github.com/dnahilman/goten/cmd/goten@latestAfter this, goten should be on your PATH. Verify with goten --help.
The CLI is generate-only (like better-auth's generate): it emits ORM model
definitions from the core schema plus the active plugins' schema. You apply them
with your ORM — for GORM, db.AutoMigrate(authmodels.AllModels()...). There is no
separate migrate command; your ORM owns DDL.
Create goten.config.yaml at the project root:
# yaml-language-server: $schema=https://raw.githubusercontent.com/dnahilman/goten/main/goten.config.schema.json
plugins:
- username # delete this line if you skipped the optional plugin
generate:
output_dir: ./internal/auth # where the generated file lands
package: authmodels # Go package name for the generated file
orm: gorm # currently only "gorm"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.
The defaults (when omitted) are output_dir: ./internal/auth, package: authmodels, orm: gorm.
goten generateThis writes a single file — internal/auth/auth_models.go by default — containing
the GORM model structs for the core schema plus every plugin listed in §4:
.
├── goten.config.yaml
└── internal/
└── auth/
└── auth_models.go # User, Session, Account, Verification (+ plugin fields)
The file is marked // Code generated by goten generate. DO NOT EDIT. and exposes
an AllModels() helper so you can pass everything to AutoMigrate in one call.
Re-run goten generate whenever you change the plugin list or bump goten; pass
-y to overwrite without the prompt.
Versioned migrations instead of AutoMigrate? Feed the generated structs to a schema tool like Atlas (
atlas-provider-gorm) to produce versioned SQL. Seeexamples/and the README for the pattern.
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"
authmodels "github.com/yourname/myapp/internal/auth" // the generated package
)
func main() {
db, err := gorm.Open(
postgres.Open("postgres://goten:goten@localhost:5432/goten?sslmode=disable"),
&gorm.Config{},
)
if err != nil {
log.Fatal(err)
}
// Apply goten's schema. AutoMigrate is additive (creates tables, adds
// columns/indexes); it never drops or rewrites. Run it on boot or as a
// one-off — for production prefer a versioned migration tool.
if err := db.AutoMigrate(authmodels.AllModels()...); 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:
-
Secretmust be ≥ 32 bytes. Goten refuses to start with a shorter key. Use a real random value in production. -
BaseURLmust 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 thegin.WrapHmounting pattern and a Gin-idiomatic middleware adapter.
Run it:
go run .With the server on :8080, walk through the full flow. Goten issues an HttpOnly
cookie (goten_session) and also returns the token in the JSON body at
session.token (prefixed g10_) for non-browser clients.
curl -i -c cookies.txt -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"}'The response body is {"user":{...},"session":{...}}. Grab the bearer token from
session.token if you want to test with Authorization headers instead of cookies:
TOKEN="g10_...paste session.token from the response..."curl -i -c cookies.txt -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"}'Cookie:
curl -i -b cookies.txt http://localhost:8080/api/auth/get-sessionBearer:
curl -i http://localhost:8080/api/auth/get-session \
-H "Authorization: Bearer $TOKEN"curl -i -b cookies.txt http://localhost:8080/api/me
# or: -H "Authorization: Bearer $TOKEN"200 OK when authenticated; 401 Unauthorized otherwise.
curl -i -b cookies.txt -X POST http://localhost:8080/api/auth/sign-outcurl -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
curl -i -b cookies.txt http://localhost:8080/api/auth/list-sessions
# Revoke one (note: camelCase "sessionId")
curl -i -b cookies.txt -X POST http://localhost:8080/api/auth/revoke-session \
-H "Content-Type: application/json" \
-d '{"sessionId":"g10_018f..."}'
# Revoke all other sessions
curl -i -b cookies.txt -X POST http://localhost:8080/api/auth/revoke-other-sessionsAdding a plugin is a 4-step affair:
# 1. Pull the plugin
go get github.com/dnahilman/goten/plugins/<plugin>
# 2. Add it to goten.config.yaml under plugins:
# plugins:
# - <plugin>
# 3. Re-generate the models (overwrites the generated file)
goten generate -y
# 4. Apply the new schema — AutoMigrate picks up the new tables/columns on bootThen 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.
The OAuth plugin (
plugins/oauth, Sign in with Google) follows the same flow but needs a provider configured andTrustedOriginsset. Seeexamples/oauth-google/and the README OAuth section.
-
More plugins / build your own — see Plugins in the README for the interfaces (
Endpoints,SchemaProvider, hook providers). - Front-end — wire a typed client with the React + TypeScript example.
-
Mount under Gin / Echo / Chi — see Goten with Gin; the pattern transfers to any framework that accepts
http.Handler. -
Harden for production — set
TrustedOriginsto your real domain(s) so non-bearer requests are origin-checked. See CSRF Protection. Behind a reverse proxy, configure the client-IP header per SECURITY.md. - 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 and you have a working app to compare against your own.