Skip to content

Go SDK

The FraiseQL Go SDK is a schema authoring SDK: you define GraphQL types, queries, and mutations in Go using struct tags and fluent builder patterns, and the FraiseQL compiler generates an optimized GraphQL API backed by your SQL views.

Terminal window
go get github.com/fraiseql/fraiseql-go

Requirements: Go 1.22+

FraiseQL Go provides struct tags and fluent builders that map your Go types to GraphQL schema constructs:

ConstructGraphQL EquivalentPurpose
Go struct + fraiseql: tagstypeDefine a GraphQL output type
fraiseql.RegisterTypes(...)type registrationRegister struct types with the schema
fraiseql.NewQuery(name)...Query fieldWire a query to a SQL view
fraiseql.NewMutation(name)...Mutation fieldDefine a mutation

Define Go structs with fraiseql: struct tags. Fields map 1:1 to keys in your backing SQL view’s .data JSONB object. Register the types with fraiseql.RegisterTypes.

schema.go
package schema
import "github.com/fraiseql/fraiseql-go/fraiseql"
type User struct {
ID fraiseql.ID `fraiseql:"id,type=ID"`
Username string `fraiseql:"username"`
Email fraiseql.Email `fraiseql:"email,type=Email"`
Bio *string `fraiseql:"bio,nullable=true"`
CreatedAt fraiseql.DateTime `fraiseql:"created_at,type=DateTime"`
}
type Post struct {
ID fraiseql.ID `fraiseql:"id,type=ID"`
Title string `fraiseql:"title"`
Slug fraiseql.Slug `fraiseql:"slug,type=Slug"`
Content string `fraiseql:"content"`
IsPublished bool `fraiseql:"is_published"`
CreatedAt fraiseql.DateTime `fraiseql:"created_at,type=DateTime"`
UpdatedAt fraiseql.DateTime `fraiseql:"updated_at,type=DateTime"`
}
func init() {
fraiseql.RegisterTypes(User{}, Post{})
}

FraiseQL provides semantic scalar types. Use the type= tag option to emit the correct GraphQL scalar type in schema.json:

import "github.com/fraiseql/fraiseql-go/fraiseql"
// Core
fraiseql.ID // UUID — use `fraiseql:"id,type=ID"`
fraiseql.Email // Validated email — use `fraiseql:"email,type=Email"`
fraiseql.Slug // URL-safe slug — use `fraiseql:"slug,type=Slug"`
fraiseql.DateTime // ISO 8601 datetime — use `fraiseql:"created_at,type=DateTime"`
fraiseql.URL // Validated URL — use `fraiseql:"website,type=URL"`

The scalar types are Go type aliases with no runtime behavior. Validation and serialization happen in the FraiseQL Rust runtime after compilation.


Input types are plain Go structs registered with fraiseql.RegisterTypes. Declare them before use so the compiler knows their shape:

schema.go
package schema
import "github.com/fraiseql/fraiseql-go/fraiseql"
type CreatePostInput struct {
Title string `fraiseql:"title"`
Content string `fraiseql:"content"`
IsPublished bool `fraiseql:"is_published"`
}
type UpdatePostInput struct {
ID fraiseql.ID `fraiseql:"id,type=ID"`
Title *string `fraiseql:"title,nullable=true"`
Content *string `fraiseql:"content,nullable=true"`
IsPublished *bool `fraiseql:"is_published,nullable=true"`
}
func init() {
fraiseql.RegisterTypes(CreatePostInput{}, UpdatePostInput{})
}

Use fraiseql.NewQuery to build query definitions and wire them to SQL views. Call .Register() to add each query to the global schema registry:

schema.go
package schema
import "github.com/fraiseql/fraiseql-go/fraiseql"
func init() {
// List query — maps to v_post view
fraiseql.NewQuery("posts").
SqlSource("v_post").
ReturnType(Post{}).
ReturnsArray(true).
Arg("is_published", "Boolean", nil, true).
Arg("limit", "Int", 20).
Arg("offset", "Int", 0).
Description("Fetch posts, optionally filtered by published status.").
Register()
// Single-item query — maps to v_post with id filter
fraiseql.NewQuery("post").
SqlSource("v_post").
ReturnType(Post{}).
Nullable(true).
Arg("id", "ID", nil).
Description("Fetch a single post by ID.").
Register()
}

Query arguments matching columns in the backing view become SQL WHERE clauses automatically. See SQL Patterns → Automatic WHERE Clauses for details.


Use fraiseql.NewMutation to build mutation definitions. Each mutation maps to a PostgreSQL function using the fn_ naming convention:

schema.go
package schema
import "github.com/fraiseql/fraiseql-go/fraiseql"
func init() {
// Maps to fn_create_post PostgreSQL function
fraiseql.NewMutation("create_post").
SqlSource("fn_create_post").
ReturnType(Post{}).
Arg("input", "CreatePostInput", nil).
Operation("insert").
InvalidatesViews([]string{"v_post"}).
Description("Create a new blog post.").
Register()
// Maps to fn_publish_post PostgreSQL function
fraiseql.NewMutation("publish_post").
SqlSource("fn_publish_post").
ReturnType(Post{}).
Arg("id", "ID", nil).
RequiresRole("editor").
Operation("update").
InvalidatesViews([]string{"v_post"}).
Description("Publish a draft post.").
Register()
// Maps to fn_delete_post PostgreSQL function
fraiseql.NewMutation("delete_post").
SqlSource("fn_delete_post").
ReturnType(Post{}).
Arg("id", "ID", nil).
RequiresRole("admin").
Operation("delete").
InvalidatesViews([]string{"v_post"}).
Description("Soft-delete a post.").
Register()
}

Each mutation maps to a PostgreSQL function in db/schema/. The Go definition is the schema; the SQL function is the implementation.

Mutations wire to PostgreSQL functions returning mutation_response. Views return (id UUID, data JSONB). See SQL Patterns for complete table, view, and function templates.


Use .RequiresRole(role) on any query or mutation builder to restrict access to callers who hold the specified role:

schema.go
func init() {
// Restrict query to users with the "member" role
fraiseql.NewQuery("my_posts").
SqlSource("v_post").
ReturnType(Post{}).
ReturnsArray(true).
RequiresRole("member").
InjectParams(map[string]string{"author_id": "jwt:sub"}).
Register()
// Restrict mutation to users with the "editor" role
fraiseql.NewMutation("create_post").
SqlSource("fn_create_post").
ReturnType(Post{}).
Arg("input", "CreatePostInput", nil).
RequiresRole("editor").
Register()
}

JWT claim injection via InjectParams lets you pass server-side values (like the authenticated user’s ID) into queries without exposing them as client-controlled arguments.


For a complete blog API schema combining all the patterns above, see the fraiseql-starter-blog repository.


Transport annotations are optional. Omit them to serve an operation via GraphQL only. Chain .RESTPath(), .RESTMethod(), .GRPCService(), and .GRPCMethod() on any query or mutation builder.

import "github.com/fraiseql/fraiseql-go"
// REST + GraphQL
fraiseql.NewQuery("posts").
SqlSource("v_post").
RESTPath("/posts").
RESTMethod("GET").
Build()
// With path parameter
fraiseql.NewQuery("post").
SqlSource("v_post").
RESTPath("/posts/{id}"). // {id} matches the id parameter
RESTMethod("GET").
Build()
// gRPC
fraiseql.NewQuery("postsGRPC").
SqlSource("v_post").
GRPCService("BlogService").
GRPCMethod("ListPosts").
Build()
// All three transports
fraiseql.NewQuery("posts").
SqlSource("v_post").
RESTPath("/posts").
RESTMethod("GET").
GRPCService("BlogService").
GRPCMethod("ListPosts").
Build()
// REST mutation
fraiseql.NewMutation("createPost").
SqlSource("create_post").
Operation("CREATE").
RESTPath("/posts").
RESTMethod("POST").
GRPCService("BlogService").
GRPCMethod("CreatePost").
Build()

Path parameters in RESTPath (e.g., {id}) must match the corresponding argument name registered on the query. A mismatch produces a compile-time error. Duplicate (method, path) pairs are also rejected at compile time.


  1. Export the schema — compiles Go types to the FraiseQL schema JSON:

    cmd/export/main.go
    package main
    import (
    "encoding/json"
    "os"
    _ "your-module/schema" // side-effect: runs init() functions
    "github.com/fraiseql/fraiseql-go/fraiseql"
    )
    func main() {
    data, err := fraiseql.GetSchemaJSON(true)
    if err != nil {
    panic(err)
    }
    os.WriteFile("schema.json", data, 0644)
    }
  2. Compile with the CLI:

    Terminal window
    fraiseql compile --schema schema.json
  3. Serve the API:

    Terminal window
    fraiseql run