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:mapas a GraphQL object type{:graphql/interface :InterfaceName}- Marks a:mapas 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:HOMEinstead 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
:Queryand:Mutationtop-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.