graphql-server.schema

GraphQL schema generation from Malli schemas.

This namespace transforms Malli schemas describing resolver functions into Lacinia-compatible GraphQL schemas. The main entry point is ->graphql-schema, which takes a map of resolver definitions and produces a complete GraphQL schema including objects, interfaces, unions, enums, and input objects.

Resolver Schema Format

Resolvers are defined using Malli :=> (function) schemas that describe 3-arity functions:

[:=> [:cat context-schema args-schema value-schema] return-schema]

Where: - context-schema - Usually :any, represents the GraphQL context - args-schema - A :map schema defining GraphQL field arguments (or :any for no args) - value-schema - Usually :any, represents the parent value in nested resolvers - return-schema - The return type schema

Type Annotations

Malli schemas use special properties to control GraphQL type generation:

  • {:graphql/type :TypeName} - Marks a :map as a GraphQL object type
  • {:graphql/interface :InterfaceName} - Marks a :map as a GraphQL interface
  • {:graphql/implements [Schema1 Schema2]} - Object implements interfaces (must reference schemas, not keywords)
  • {:graphql/scalar :ScalarName} - Maps a schema to a GraphQL scalar type (e.g., :Date, :Uuid)
  • {:graphql/hidden true} - Excludes a field from the GraphQL schema
  • {:graphql/name :FieldName} - Overrides the default camelCase field name in GraphQL (e.g., use :HOME instead of :home)

Examples

Simple Query

(->graphql-schema
  {[:Query :hello]
   [[:=> [:cat :any :any :any] :string]
    (fn [context args value] "world")]})

Query with Arguments

(->graphql-schema
  {[:Query :greet]
   [[:=> [:cat :any [:map [:name :string]] :any] :string]
    (fn [context {:keys [name]} value]
      (str "Hello, " name))]})

Custom Object Types

(def User
  [:map {:graphql/type :User}
   [:id :uuid]
   [:name :string]
   [:email :string]])

(->graphql-schema
  {[:Query :user]
   [[:=> [:cat :any [:map [:id :uuid]] :any] User]
    (fn [context {:keys [id]} value]
      (fetch-user id))]})

Interfaces and Implementation

Interface schemas must be defined and referenced as schema values in :graphql/implements:

(def Node
  [:map {:graphql/interface :Node}
   [:id :uuid]])

(def User
  [:map {:graphql/type :User
         :graphql/implements [Node]}  ; Reference schema, not keyword
   [:id :uuid]
   [:name :string]])

Union Types

(def SearchResult
  [:multi {:graphql/type :SearchResult
           :dispatch :type}
   [:user [:map {:graphql/type :User} ...]]
   [:org [:map {:graphql/type :Organization} ...]]])

Mutations with Input Objects

Input objects are automatically created for mutation arguments:

{[:Mutation :createUser]
 [[:=> [:cat :any
        [:map [:input [:map {:graphql/type :CreateUserInput}
                       [:name :string]
                       [:email :string]]]]
        :any]
   [:map {:graphql/type :User} ...]]
  (fn [context {:keys [input]} value]
    (create-user input))]}

Output Schema Format

The generated schema is a map containing:

{:objects {...}          ; GraphQL object types and Query/Mutation
 :interfaces {...}       ; GraphQL interface types
 :unions {...}           ; GraphQL union types
 :enums {...}            ; GraphQL enum types
 :input-objects {...}}   ; GraphQL input object types (for mutations)

Naming Conventions

  • Field names are converted to camelCase
  • Type names use PascalCase
  • Only :Query and :Mutation top-level objects are processed

->graphql-schema

(->graphql-schema resolver-map)

Converts resolver definitions into a Lacinia-compatible GraphQL schema.

Takes a resolver-map where keys are [object-type field-name] tuples (e.g., [:Query :user], [:Subscription :gameUpdated]) and values are [malli-schema resolver-fn] or [malli-schema resolver-fn description] tuples.

The Malli schema must describe a function using [:=> [:cat context args value] return-type]. For subscriptions, the return type represents the type of each streamed value. The optional description string will be added to the GraphQL field.

Returns a map containing :objects, :subscriptions, :interfaces, :unions, :enums, and :input-objects with the complete GraphQL schema. Subscription fields are placed under :subscriptions (Lacinia’s native format) rather than under :objects.

See the namespace documentation for detailed examples and usage patterns.