Skip to content

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.

Terminal window
axel init

This writes a starter project:

axel.yaml # config: schema-path, migrations-dir, database-url
axel/schema.asl # a Base abstract type + a starter User
axel/migrations/ # empty; migrations land here

axel.yaml points the database URL at an env var so secrets stay out of the file:

schema-path: axel/schema.asl
migrations-dir: axel/migrations
database-url: $env.DATABASE_URL

Set that to your Postgres — local, Supabase, or Neon:

Terminal window
export DATABASE_URL='postgresql://user:pass@localhost:5432/app?sslmode=disable'

The starter axel/schema.asl already defines a reusable Base (uuid primary key

  • created_at / updated_at) and a User. Add a Post linked to User:
type Post extends Base {
required title: str;
content: str;
required author: User;
}

Type-check the schema at any time — no database needed:

Terminal window
axel validate

Generate a migration from the schema, then apply it to the database:

Terminal window
axel diff -n init # writes axel/migrations/0001_init
axel up # applies pending migrations

axel up records applied migrations in an _axel_migrations table, so it’s safe to re-run. See the CLI reference for diff / up / down.

Queries live in .aql files. Create queries/list_post.aql and queries/get_user.aql:

list_post.aql
multi select Post { id, title, content };
get_user.aql
select User { id, name, email } filter .id = $id<uuid>;

multi select returns many rows ([]ListPostRow); a plain select returns a single row (*GetUserRow).

Terminal window
axel codegen -g go -o ./gen --option package=gen

Axel 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.aqlrunner.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:

Terminal window
go get github.com/struckchure/axel github.com/jackc/pgx/v5
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.

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},
)

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)