F# SDK
The FraiseQL F# SDK is a schema authoring SDK: you define GraphQL types, queries, and mutations using F# computation expressions, and the FraiseQL compiler generates an optimized GraphQL API backed by your SQL views.
Installation
Section titled “Installation”dotnet add package FraiseQL.FSharpRequirements: .NET 8+ / F# 7+
Core Concepts
Section titled “Core Concepts”FraiseQL F# uses computation expressions — F#‘s idiomatic DSL mechanism — to define schema constructs without global mutable state:
| Builder | GraphQL Equivalent | Purpose |
|---|---|---|
fraiseql { type' ... } | type | Accumulate type and query definitions into a schema |
TypeCEBuilder | field declarations | Define a GraphQL output or input type |
QueryCEBuilder | Query field | Wire a query to a SQL view |
MutationCEBuilder | Mutation field | Define a mutation |
FieldBuilder | field metadata | Configure a single field’s type and nullability |
Defining a Schema
Section titled “Defining a Schema”All definitions are composed into an IntermediateSchema value using the fraiseql { } computation expression:
open FraiseQL
let schema = fraiseql { type' "User" (TypeCEBuilder("User") { sqlSource "v_user" description "A registered user" field "id" "ID" { nullable false } field "username" "String" { nullable false } field "email" "Email" { nullable false } field "bio" "String" { nullable true } field "createdAt" "DateTime" { nullable false } })
type' "Post" (TypeCEBuilder("Post") { sqlSource "v_post" field "id" "ID" { nullable false } field "title" "String" { nullable false } field "slug" "Slug" { nullable false } field "content" "String" { nullable false } field "isPublished" "Boolean" { nullable false } field "createdAt" "DateTime" { nullable false } }) }Defining Inputs
Section titled “Defining Inputs”Input types use isInput true on TypeCEBuilder:
let schema = fraiseql { type' "CreatePostInput" (TypeCEBuilder("CreatePostInput") { isInput true field "title" "String" { nullable false } field "content" "String" { nullable false } field "isPublished" "Boolean" { nullable false } }) }Defining Queries
Section titled “Defining Queries”Use QueryCEBuilder inside the fraiseql { } block:
let schema = fraiseql { // List query query "posts" (QueryCEBuilder("posts") { returnType "Post" returnsList true sqlSource "v_post" description "Fetch posts, optionally filtered by published status" arg "isPublished" "Boolean" { nullable true } arg "limit" "Int" { defaultValue 20 } arg "offset" "Int" { defaultValue 0 } })
// Single-item query query "post" (QueryCEBuilder("post") { returnType "Post" sqlSource "v_post" nullable true arg "id" "ID" { nullable false } }) }How Arguments Become WHERE Clauses
Section titled “How Arguments Become WHERE Clauses”Query arguments matching columns in the backing view become SQL WHERE clauses automatically. See SQL Patterns → Automatic WHERE Clauses for details.
Defining Mutations
Section titled “Defining Mutations”Use MutationCEBuilder inside the fraiseql { } block:
let schema = fraiseql { mutation "createPost" (MutationCEBuilder("createPost") { sqlSource "fn_create_post" returnType "Post" operation "insert" invalidatesViews [ "v_post" ] description "Create a new blog post" arg "input" "CreatePostInput" { nullable false } })
mutation "deletePost" (MutationCEBuilder("deletePost") { sqlSource "fn_delete_post" returnType "Post" operation "delete" requiresRole "admin" invalidatesViews [ "v_post" ] arg "id" "ID" { nullable false } }) }The SQL Side
Section titled “The SQL Side”Mutations wire to PostgreSQL functions returning mutation_response. See SQL Patterns for complete function templates.
Authorization
Section titled “Authorization”Use requiresRole on query or mutation builders:
query "myPosts" (QueryCEBuilder("myPosts") { sqlSource "v_post" returnType "Post" returnsList true requiresRole "member" injectParams [ "authorId", "jwt:sub" ]})Field-level scope restrictions use scope in FieldBuilder:
type' "User" (TypeCEBuilder("User") { sqlSource "v_user" field "id" "ID" { nullable false } field "email" "Email" { nullable false; scope "user:read:email" }})Transport Annotations
Section titled “Transport Annotations”Transport annotations are optional. Omit them to serve an operation via GraphQL only. Use [<Query(...)>] and [<Mutation(...)>] .NET attributes with SqlSource, RestPath, and RestMethod named parameters. gRPC endpoints are auto-generated when [grpc] is enabled — no per-operation annotation needed. See gRPC Transport.
open FraiseQL.SDK
[<Query(SqlSource = "v_post", RestPath = "/posts", RestMethod = "GET")>]let posts (limit: int) : Post list = []
[<Query(SqlSource = "v_post", RestPath = "/posts/{id}", RestMethod = "GET")>]let post (id: System.Guid) : Post option = None
[<Mutation(SqlSource = "create_post", Operation = "CREATE", RestPath = "/posts", RestMethod = "POST")>]let createPost (title: string) (authorId: System.Guid) : Post = Unchecked.defaultof<Post>Path parameters in RestPath (e.g., {id}) must match function parameter names exactly. A mismatch produces a compile-time error. Duplicate (method, path) pairs are also rejected at compile time.
Build and Export
Section titled “Build and Export”-
Export the schema — converts your
IntermediateSchematoschema.json:Export/Program.fs open FraiseQLopen MyApp[<EntryPoint>]let main _ =Schema.schema|> SchemaExporter.exportToFile "schema.json"0Run the export:
Terminal window dotnet run --project Export -
Compile with the FraiseQL CLI:
Terminal window fraiseql compile --schema schema.json -
Serve the API:
Terminal window fraiseql run
HTTP Client
Section titled “HTTP Client”FraiseQLClient provides async GraphQL queries and mutations from F# applications:
open FraiseQL
let options = { Endpoint = System.Uri "https://api.example.com/graphql" Authorization = Some "Bearer your-token" Retry = Some { MaxAttempts = 3; BaseDelay = 200; MaxDelay = 5000; Jitter = true } }
use client = new FraiseQLClient(options)
// Querylet! users = client.QueryAsync<User list> """ query { users { id username email } } """
// Mutationlet! post = client.MutateAsync<Post>( """mutation($input: CreatePostInput!) { createPost(input: $input) { id title } }""", {| input = {| title = "Hello"; content = "World"; isPublished = true |} |})The exception hierarchy (FraiseQLException, GraphQLException, AuthenticationException, RateLimitException, FraiseQLTimeoutException, NetworkException) is identical to the C# SDK.
Next Steps
Section titled “Next Steps”- SDK Overview — how compile-time authoring works
- SQL Patterns — view and function conventions
- Your First API — full tutorial
- All SDKs — compare languages