GraphQL implementation in pure Clojure
See the codeLacinia Manual | Lacinia Tutorial | API Documentation
This library is a full implementation of Facebook's GraphQL specification.
Lacinia should be viewed as roughly analogous to the official reference JavaScript implementation. In other words, it is a backend-agnostic GraphQL query execution engine. Lacinia is not an Object Relational Mapper ... it's simply the implementation of a contract sitting between the GraphQL client and your data.
Lacinia features:
An EDN-based schema language, or use GraphQL's Interface Definition Language.
High performance parser for GraphQL queries, built on Antlr4.
Efficient and asynchronous query execution.
Full support for GraphQL types, interfaces, unions, enums, input objects, and custom scalars.
Union types in SDL now support an optional leading vertical bar (|) before the first member, following the GraphQL specification. For example:
union Searchable =
| Business
| Employee
Full support for GraphQL subscriptions.
Full support of inline and named query fragments.
Full support for GraphQL Schema Introspection.
Lacinia has been developed with a set of core philosophies:
Prefer data over macros and other tricks: Compose your schema in whatever mix of data and code works for you.
Embrace Clojure: Use EDN data, keywords, functions, and persistent data structures.
Keep it simple: You provide the schema and a handful of functions to resolve data, and Lacinia does the rest.
Do the right thing: apply reasonable defaults without a lot of "magic".
This library can be plugged into any Clojure HTTP pipeline. The companion library lacinia-pedestal provides full HTTP support, including GraphQL subscriptions, for Pedestal.
An externally developed library, duct-lacinia, provides similar capability for Duct.
For more detailed documentation, read the manual.
When using Lacinia as a dependency, you may need to prepare the library:
clojure -X:deps prep
This is required because Lacinia uses precompiled ANTLR parsers that need to be built before use.
GraphQL starts with a schema definition of types that can be queried.
A schema starts as an EDN file; the example below demonstrates a small subset of the available options:
{:enums
{:Episode
{:description "The episodes of the original Star Wars trilogy."
:values [:NEWHOPE :EMPIRE :JEDI]}}
:objects
{:Droid
{:fields {:id {:type Int}
:primaryFunctions {:type (list String)}
:name {:type String}
:appearsIn {:type (list :Episode)}}}
:Human
{:fields {:id {:type Int}
:name {:type String}
:homePlanet {:type String}
:appearsIn {:type (list :Episode)}}}
:Query
{:fields {:hero {:type (non-null :Human)
:args {:episode {:type :Episode}}}
:droid {:type :Droid
:args {:id {:type String
:default-value "2001"}}}}}}}
The fields of the special Query object define the query operations available; with this schema,
a client can find the Human hero of an episode, or find a Droid by its id.
A schema alone describes what data is available to clients, but doesn't identify where the data comes from; that's the job of a field resolver.
A field resolver is just a function which is passed the application context, a map of arguments values, and a resolved value from a parent field. The field resolver returns a value consistent with the type of the field; most field resolvers return a Clojure map or record, or a list of those. Lacinia then uses the GraphQL query to select fields of that value to return in the response.
Here's what a very opinionated get-hero field resolver might look like:
(defn get-hero
[context arguments value]
(let [{:keys [episode]} arguments]
(if (= episode :NEWHOPE)
{:id 1000
:name "Luke"
:homePlanet "Tatooine"
:appearsIn ["NEWHOPE" "EMPIRE" "JEDI"]}
{:id 2000
:name "Lando Calrissian"
:homePlanet "Socorro"
:appearsIn ["EMPIRE" "JEDI"]})))
In this greatly simplified example, the field resolver can simply return the resolved value. Field resolvers that return multiple values return a list, vector, or set of values.
In real applications, a field resolver might execute a query against a database, or send a request to another web service.
After injecting resolvers, it is necessary to compile the schema; this step performs validations, provides defaults, and organizes the schema for efficient execution of queries.
This needs only be done once, in application startup code:
(require '[clojure.edn :as edn]
'[com.walmartlabs.lacinia.util :refer [inject-resolvers]]
'[com.walmartlabs.lacinia.schema :as schema])
(def star-wars-schema
(-> "schema.edn"
slurp
edn/read-string
(inject-resolvers {:Query/hero get-hero
:Query/droid (constantly {})})
schema/compile))
With the compiled application available, it can be used to execute requests; this typically occurs inside a Ring handler function:
(require '[com.walmartlabs.lacinia :refer [execute]]
'[clojure.data.json :as json])
(defn handler [request]
{:status 200
:headers {"Content-Type" "application/json"}
:body (let [query (get-in request [:query-params :query])
result (execute star-wars-schema query nil nil)]
(json/write-str result))})
Lacinia doesn't know about the web tier at all, it just knows about parsing and executing queries against a compiled schema. A companion library, lacinia-pedestal, is one way to expose your schema on the web.
Clients will typically send a JSON POST request, with a query key containing the GraphQL query document:
{
hero {
id
name
}
}
The execute function returns EDN data that can be easily converted to JSON.
The :data key contains the value requested for the hero query in the request.
{:data
{:hero {:id 2000
:name "Lando Calrissian"}}}
This example request has no errors, and contained only a single query. GraphQL supports multiple queries in a single request. There may be errors executing the query, Lacinia will process as much as it can, and will report errors in the :errors key.
One of the benefits of GraphQL is that the client has the power to rename fields in the response:
{
hero(episode: NEWHOPE) {
movies: appearsIn
}
}
{:data {:hero {:movies [:NEWHOPE :EMPIRE :JEDI]}}}
This is just an overview, far more detail is available in the manual.
This library has been used in production at Walmart since 2017, going through a very long beta period as it evolved; we transitioned to a 1.0 release on 9 Oct 2021.
To use this library with Clojure 1.8, you must include a dependency on clojure-future-spec.
More details are in the manual.
Lacinia uses ANTLR-generated parsers for GraphQL query and schema parsing.
Compile the Java sources:
clojure -T:build compile-java
This compiles the ANTLR-generated parser classes into target/classes.
If you modify the ANTLR grammar files (.g4), regenerate the Java parsers:
./codegen.sh
clojure -X:dev:test
Copyright © 2017-2025 WalmartLabs
Distributed under the Apache License, Version 2.0.
494 followers · starred Oct 2021
255 followers · starred Sep 2017
378 followers · starred Jun 2019
521 followers · starred Aug 2022
GraphQL implementation in pure Clojure
See the codeLacinia Manual | Lacinia Tutorial | API Documentation
This library is a full implementation of Facebook's GraphQL specification.
Lacinia should be viewed as roughly analogous to the official reference JavaScript implementation. In other words, it is a backend-agnostic GraphQL query execution engine. Lacinia is not an Object Relational Mapper ... it's simply the implementation of a contract sitting between the GraphQL client and your data.
Lacinia features:
An EDN-based schema language, or use GraphQL's Interface Definition Language.
High performance parser for GraphQL queries, built on Antlr4.
Efficient and asynchronous query execution.
Full support for GraphQL types, interfaces, unions, enums, input objects, and custom scalars.
Union types in SDL now support an optional leading vertical bar (|) before the first member, following the GraphQL specification. For example:
union Searchable =
| Business
| Employee
Full support for GraphQL subscriptions.
Full support of inline and named query fragments.
Full support for GraphQL Schema Introspection.
Lacinia has been developed with a set of core philosophies:
Prefer data over macros and other tricks: Compose your schema in whatever mix of data and code works for you.
Embrace Clojure: Use EDN data, keywords, functions, and persistent data structures.
Keep it simple: You provide the schema and a handful of functions to resolve data, and Lacinia does the rest.
Do the right thing: apply reasonable defaults without a lot of "magic".
This library can be plugged into any Clojure HTTP pipeline. The companion library lacinia-pedestal provides full HTTP support, including GraphQL subscriptions, for Pedestal.
An externally developed library, duct-lacinia, provides similar capability for Duct.
For more detailed documentation, read the manual.
When using Lacinia as a dependency, you may need to prepare the library:
clojure -X:deps prep
This is required because Lacinia uses precompiled ANTLR parsers that need to be built before use.
GraphQL starts with a schema definition of types that can be queried.
A schema starts as an EDN file; the example below demonstrates a small subset of the available options:
{:enums
{:Episode
{:description "The episodes of the original Star Wars trilogy."
:values [:NEWHOPE :EMPIRE :JEDI]}}
:objects
{:Droid
{:fields {:id {:type Int}
:primaryFunctions {:type (list String)}
:name {:type String}
:appearsIn {:type (list :Episode)}}}
:Human
{:fields {:id {:type Int}
:name {:type String}
:homePlanet {:type String}
:appearsIn {:type (list :Episode)}}}
:Query
{:fields {:hero {:type (non-null :Human)
:args {:episode {:type :Episode}}}
:droid {:type :Droid
:args {:id {:type String
:default-value "2001"}}}}}}}
The fields of the special Query object define the query operations available; with this schema,
a client can find the Human hero of an episode, or find a Droid by its id.
A schema alone describes what data is available to clients, but doesn't identify where the data comes from; that's the job of a field resolver.
A field resolver is just a function which is passed the application context, a map of arguments values, and a resolved value from a parent field. The field resolver returns a value consistent with the type of the field; most field resolvers return a Clojure map or record, or a list of those. Lacinia then uses the GraphQL query to select fields of that value to return in the response.
Here's what a very opinionated get-hero field resolver might look like:
(defn get-hero
[context arguments value]
(let [{:keys [episode]} arguments]
(if (= episode :NEWHOPE)
{:id 1000
:name "Luke"
:homePlanet "Tatooine"
:appearsIn ["NEWHOPE" "EMPIRE" "JEDI"]}
{:id 2000
:name "Lando Calrissian"
:homePlanet "Socorro"
:appearsIn ["EMPIRE" "JEDI"]})))
In this greatly simplified example, the field resolver can simply return the resolved value. Field resolvers that return multiple values return a list, vector, or set of values.
In real applications, a field resolver might execute a query against a database, or send a request to another web service.
After injecting resolvers, it is necessary to compile the schema; this step performs validations, provides defaults, and organizes the schema for efficient execution of queries.
This needs only be done once, in application startup code:
(require '[clojure.edn :as edn]
'[com.walmartlabs.lacinia.util :refer [inject-resolvers]]
'[com.walmartlabs.lacinia.schema :as schema])
(def star-wars-schema
(-> "schema.edn"
slurp
edn/read-string
(inject-resolvers {:Query/hero get-hero
:Query/droid (constantly {})})
schema/compile))
With the compiled application available, it can be used to execute requests; this typically occurs inside a Ring handler function:
(require '[com.walmartlabs.lacinia :refer [execute]]
'[clojure.data.json :as json])
(defn handler [request]
{:status 200
:headers {"Content-Type" "application/json"}
:body (let [query (get-in request [:query-params :query])
result (execute star-wars-schema query nil nil)]
(json/write-str result))})
Lacinia doesn't know about the web tier at all, it just knows about parsing and executing queries against a compiled schema. A companion library, lacinia-pedestal, is one way to expose your schema on the web.
Clients will typically send a JSON POST request, with a query key containing the GraphQL query document:
{
hero {
id
name
}
}
The execute function returns EDN data that can be easily converted to JSON.
The :data key contains the value requested for the hero query in the request.
{:data
{:hero {:id 2000
:name "Lando Calrissian"}}}
This example request has no errors, and contained only a single query. GraphQL supports multiple queries in a single request. There may be errors executing the query, Lacinia will process as much as it can, and will report errors in the :errors key.
One of the benefits of GraphQL is that the client has the power to rename fields in the response:
{
hero(episode: NEWHOPE) {
movies: appearsIn
}
}
{:data {:hero {:movies [:NEWHOPE :EMPIRE :JEDI]}}}
This is just an overview, far more detail is available in the manual.
This library has been used in production at Walmart since 2017, going through a very long beta period as it evolved; we transitioned to a 1.0 release on 9 Oct 2021.
To use this library with Clojure 1.8, you must include a dependency on clojure-future-spec.
More details are in the manual.
Lacinia uses ANTLR-generated parsers for GraphQL query and schema parsing.
Compile the Java sources:
clojure -T:build compile-java
This compiles the ANTLR-generated parser classes into target/classes.
If you modify the ANTLR grammar files (.g4), regenerate the Java parsers:
./codegen.sh
clojure -X:dev:test
Copyright © 2017-2025 WalmartLabs
Distributed under the Apache License, Version 2.0.
494 followers · starred Oct 2021
255 followers · starred Sep 2017
378 followers · starred Jun 2019
521 followers · starred Aug 2022