Skip to content

Builder API ​

The Builder API is the main interface for creating type-safe GraphQL operations in GQLB. It provides fluent methods for building queries, mutations, subscriptions, and fragments.

Root Builder (b) ​

The root builder is imported from your generated code and provides access to all operation types:

typescript
import { b } from './generated'

// Available operations
b.query()        // Create queries
b.mutation()     // Create mutations  
b.subscription() // Create subscriptions
b.fragment()     // Create fragments

Query Builder ​

Basic Syntax ​

typescript
b.query(name: string, builder: BuilderFunction)
b.query(name: string, variables: Variables, builder: BuilderFunction)

Examples ​

typescript
// Simple query without variables
const USERS_QUERY = b.query('GetUsers', (b) => [
  b.users(b => [
    b.id(),
    b.name(),
  ])
])

// Query with variables
const USER_QUERY = b.query('GetUser', { id: 'ID!' }, (b, v) => [
  b.user({ id: v.id }, b => [
    b.id(),
    b.name(),
    b.email(),
  ])
])

Mutation Builder ​

Basic Syntax ​

typescript
b.mutation(name: string, builder: BuilderFunction)
b.mutation(name: string, variables: Variables, builder: BuilderFunction)

Examples ​

typescript
// Simple mutation
const DELETE_USER = b.mutation('DeleteUser', { id: 'ID!' }, (b, v) => [
  b.deleteUser({ id: v.id }, b => [
    b.success(),
    b.message(),
  ])
])

// Complex mutation with input types
const CREATE_POST = b.mutation('CreatePost', { 
  input: 'CreatePostInput!' 
}, (b, v) => [
  b.createPost({ input: v.input }, b => [
    b.id(),
    b.title(),
    b.author(b => [
      b.id(),
      b.name(),
    ])
  ])
])

Subscription Builder ​

Basic Syntax ​

typescript
b.subscription(name: string, builder: BuilderFunction)
b.subscription(name: string, variables: Variables, builder: BuilderFunction)

Examples ​

typescript
// Real-time notifications
const NOTIFICATIONS_SUB = b.subscription('NotificationUpdates', {
  userId: 'ID!'
}, (b, v) => [
  b.notificationAdded({ userId: v.userId }, b => [
    b.id(),
    b.message(),
    b.createdAt(),
  ])
])

Fragment Builder ​

Basic Syntax ​

typescript
b.fragment(name: string, typeCondition: string, builder: BuilderFunction)

Examples ​

typescript
// Reusable user fragment
const USER_FRAGMENT = b.fragment('UserInfo', 'User', (b) => [
  b.id(),
  b.name(),
  b.email(),
  b.avatar(),
])

// Use in queries
const QUERY_WITH_FRAGMENT = b.query('GetUser', (b) => [
  b.user(b => [
    b.__fragment(USER_FRAGMENT)
  ])
])

Field Selection ​

Scalar Fields ​

Select scalar fields directly:

typescript
const QUERY = b.query('ScalarFields', (b) => [
  b.user(b => [
    b.id(),          // String!
    b.name(),        // String!
    b.email(),       // String (nullable)
    b.age(),         // Int (nullable)
    b.isActive(),    // Boolean!
  ])
])

Object Fields ​

Object fields require a sub-builder:

typescript
const QUERY = b.query('ObjectFields', (b) => [
  b.user(b => [
    b.profile(b => [      // User.profile: Profile
      b.bio(),
      b.website(),
    ]),
    b.settings(b => [     // User.settings: Settings
      b.theme(),
      b.notifications(),
    ])
  ])
])

List Fields ​

Lists are handled automatically:

typescript
const QUERY = b.query('ListFields', (b) => [
  b.user(b => [
    b.posts(b => [        // User.posts: [Post!]!
      b.id(),
      b.title(),
    ]),
    b.tags(),             // User.tags: [String!] (scalar list)
  ])
])

Field Arguments ​

Required Arguments ​

Fields with required arguments must receive them:

typescript
const QUERY = b.query('RequiredArgs', (b) => [
  b.user({ id: "123" }, b => [    // id is required
    b.name()
  ])
])

Optional Arguments ​

Optional arguments can be omitted:

typescript
const QUERY = b.query('OptionalArgs', (b) => [
  // Without optional arguments
  b.posts(b => [
    b.title()
  ]),
  
  // With optional arguments
  b.posts({ limit: 10, offset: 0 }, b => [
    b.title()
  ])
])

Argument Types ​

Arguments are typed based on your schema:

typescript
const QUERY = b.query('TypedArgs', (b) => [
  b.posts({
    limit: 10,              // Int
    published: true,        // Boolean
    authorId: "123",        // ID
    tags: ["tech", "js"],   // [String!]
    filters: {              // Custom input type
      minDate: "2023-01-01",
      maxDate: "2023-12-31"
    }
  }, b => [
    b.title()
  ])
])

Field Aliases ​

Use aliases to select the same field multiple times:

typescript
const QUERY = b.query('Aliases', (b) => [
  // Third parameter is the alias
  b.user({ id: "1" }, b => [
    b.name()
  ], "firstUser"),
  
  b.user({ id: "2" }, b => [
    b.name()
  ], "secondUser")
])

Inline Fragments ​

Use __on for inline fragments on unions/interfaces:

typescript
const QUERY = b.query('InlineFragments', (b) => [
  b.search({ query: "test" }, b => [
    // SearchResult is a union of User | Post
    b.__on("User", b => [
      b.name(),
      b.email(),
    ]),
    b.__on("Post", b => [
      b.title(),
      b.content(),
    ])
  ])
])

Fragment Spreads ​

Include fragments using __fragment:

typescript
const USER_FIELDS = b.fragment('UserFields', 'User', (b) => [
  b.id(),
  b.name(),
  b.email(),
])

const QUERY = b.query('WithFragment', (b) => [
  b.user(b => [
    b.__fragment(USER_FIELDS)
  ])
])

Builder Function Parameters ​

Without Variables ​

typescript
(b: Builder) => Selection[]

With Variables ​

typescript
(b: Builder, v: Variables) => Selection[]

Builder Object Methods ​

The builder object b provides methods for:

  • Field selection: b.fieldName()
  • Inline fragments: b.__on(type, builder)
  • Fragment spreads: b.__fragment(fragment)

Operation Methods ​

.document() ​

Get the GraphQL document:

typescript
const query = USERS_QUERY.document()
// Returns: DocumentNode suitable for GraphQL clients

Type Information ​

Operations carry type information for extracting result and variable types:

typescript
type Data = OutputOf<typeof USERS_QUERY>
type Variables = VariablesOf<typeof USER_QUERY>

Builder Validation ​

GQLB validates your builder usage at compile time:

typescript
// ✅ Valid
const VALID = b.query('Valid', (b) => [
  b.existingField(b => [
    b.nestedField()
  ])
])

// ❌ TypeScript errors
const INVALID = b.query('Invalid', (b) => [
  b.nonExistentField(),        // Error: field doesn't exist
  b.requiredArgField(b => [    // Error: missing required arguments
    b.field()
  ])
])

Performance Notes ​

  • Builders use lazy evaluation - documents are generated on first .document() call
  • Generated code has zero runtime overhead
  • Complex builders are efficiently tree-shaken by bundlers
  • Fragment reuse reduces code duplication

Next Steps ​

Released under the MIT License.