Go — Integrations
A full walkthrough: scaffold a project, design a schema, apply migrations, then generate and use Axel’s typed Go client. See Code Generation for the generator reference and Schema Language for ASL details.
1. Scaffold the project
Section titled “1. Scaffold the project”axel initThis writes a starter project:
axel.yaml # config: schema-path, migrations-dir, database-urlaxel/schema.asl # a Base abstract type + a starter Useraxel/migrations/ # empty; migrations land hereaxel.yaml points the database URL at an env var so secrets stay out of the file:
schema-path: axel/schema.aslmigrations-dir: axel/migrationsdatabase-url: $env.DATABASE_URLSet that to your Postgres — local, Supabase, or Neon:
export DATABASE_URL='postgresql://user:pass@localhost:5432/app?sslmode=disable'2. Design the schema
Section titled “2. Design the schema”The starter axel/schema.asl already defines a reusable Base (uuid primary key
created_at/updated_at) and aUser. Add aPostlinked toUser:
type Post extends Base { required title: str; content: str; required author: User;}Type-check the schema at any time — no database needed:
axel validate3. Diff and apply
Section titled “3. Diff and apply”Generate a migration from the schema, then apply it to the database:
axel diff -n init # writes axel/migrations/0001_initaxel up # applies pending migrationsaxel up records applied migrations in an _axel_migrations table, so it’s safe
to re-run. See the CLI reference for diff / up / down.
4. Write a query
Section titled “4. Write a query”Queries live in .aql files. Create queries/list_post.aql and
queries/get_user.aql:
multi select Post { id, title, content };select User { id, name, email } filter .id = $id<uuid>;multi select returns many rows ([]ListPostRow); a plain select returns a
single row (*GetUserRow).
5. Generate the client
Section titled “5. Generate the client”axel codegen -g go -o ./gen --option package=genAxel auto-discovers every *.aql file under the project directory
and emits a gen/ package (runner.go, models.go, one file per query). A
query’s filename becomes a PascalCase method: list_post.aql →
runner.Query.ListPost(ctx). The package is named generated unless you override
it with --option package=.... (Or name files explicitly: -q 'queries/*.aql'.)
The generated package imports Axel’s runtime, so add Axel and pgx to your module:
go get github.com/struckchure/axel github.com/jackc/pgx/v56. Connect and call
Section titled “6. Connect and call”package main
import ( "context" "log" "os"
"github.com/jackc/pgx/v5/pgxpool" gen "github.com/you/app/gen")
func main() { ctx := context.Background() db, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL")) if err != nil { log.Fatal(err) } defer db.Close()
runner := gen.NewRunner(db)
posts, err := runner.Query.ListPost(ctx) // []ListPostRow user, err := runner.Query.GetUser(ctx, gen.GetUserParams{ID: id}) // *GetUserRow _ = posts _ = user}Params and rows are generated structs (GetUserParams, ListPostRow) with db
and json tags; datetime maps to time.Time, nullable columns to pointers
(*string), and single-row queries return *XxxRow.
Dynamic escape hatch
Section titled “Dynamic escape hatch”For ad-hoc queries that aren’t in a .aql file, runner.Run executes raw AQL:
rows, err := runner.Run(ctx, `select Post { id } filter .author.id = $author`, map[string]any{"author": author},)Transactions
Section titled “Transactions”To run queries inside a transaction, use runner.WithDB(tx) or gen.NewQueries(tx):
tx, err := db.Begin(ctx)if err != nil { log.Fatal(err)}defer tx.Rollback(ctx)
q := runner.WithDB(tx)// or: q := gen.NewQueries(tx)
user, err := q.GetUser(ctx, gen.GetUserParams{ID: id})if err != nil { return err}
return tx.Commit(ctx)