Lacinia - GraphQL for Clojure

Lacinia is a library for implementing Facebook’s GraphQL specification in idiomatic Clojure.

GraphQL is a way for clients to efficiently obtain data from servers.

Compared to traditional REST approaches, GraphQL ensures that clients can access exactly the data that they need (and no more), and do so with fewer round-trips to the server.

This is especially useful for mobile clients, where bandwidth is always at a premium.

In addition, GraphQL is self describing; the shape of the data that can be exposed, and the queries by which that data can be accessed, are all accessible using GraphQL queries. This allows for sophisticated, adaptable clients, such as the in-browser GraphQL IDE graphiql.

Warning

This library is still under active development and should not be considered complete. If you would like to contribute, please create a pull request.

Although GraphQL is quite adept at handling requests from client web browsers and responding with JSON, it is also exceptionally useful for allowing backend systems to communicate.

Using this library

This library aims to maintain feature parity to that of the official reference JavaScript implementation and be fully compliant with the GraphQL specification.

Lacinia can be plugged into any Clojure HTTP pipeline. The companion library lacinia-pedestal provides full HTTP support, including GraphQL subscriptions, for Pedestal.

Overview

A GraphQL server starts with a schema of exposed types.

This GraphQL schema is described as an EDN data structure:

{:enums
 {:episode
  {:description "The episodes of the original Star Wars trilogy."
   :values [:NEWHOPE :EMPIRE :JEDI]}}

 :interfaces
 {:character
  {:fields {:id {:type String}
            :name {:type String}
            :appears_in {:type (list :episode)}
            :friends {:type (list :character)}}}}

 :objects
 {:droid
  {:implements [:character]
   :fields {:id {:type String}
            :name {:type String}
            :appears_in {:type (list :episode)}
            :friends {:type (list :character)
                      :resolve :friends}
            :primary_function {:type (list String)}}}

  :human
  {:implements [:character]
   :fields {:id {:type String}
            :name {:type String}
            :appears_in {:type (list :episode)}
            :friends {:type (list :character)
                      :resolve :friends}
            :home_planet {:type String}}}}

 :queries
 {:hero {:type (non-null :character)
         :args {:episode {:type :episode}}
         :resolve :hero}

  :human {:type (non-null :human)
          :args {:id {:type String
                      :default-value "1001"}}
          :resolve :human}

  :droid {:type :droid
          :args {:id {:type String
                      :default-value "2001"}}
          :resolve :droid}}}

The schema defines all the data that could possibly be queried by a client.

To make this schema useful, field resolvers must be added to it. These functions are responsible for doing the real work (querying databases, communicating with other servers, and so forth). These are attached to the schema after it is read from an EDN file, using the placeholder keywords in the schema, such as :resolve :droid.

The client uses the GraphQL query language to specify exactly what data should be returned in the result map:

{
  hero {
    id
    name
    friends {
      name
    }
  }
}

This translates to “run the hero query; return the default hero’s id and name, and friends; just return the name of each friend.”

Lacinia will return this as Clojure data:

{:data
 {:hero
  {:id "2001"
   :name "R2-D2"
   :friends [{:name "Luke Sykwalker"
              :name "Han Solo"
              :name "Leia Organa"}]}}}

This is because R2-D2 is, of course, considered the hero of the Star Wars trilogy.

This Clojure data can be trivially converted into JSON or other formats when Lacinia is used as part of an HTTP server application.

A key takeaway: GraphQL is a contract between a client and a server; it doesn’t know or care where the data comes from; that’s the province of the field resolvers. That’s great news: it means Lacinia is equally adept at pulling data out of a single database as it is at integrating and organizing data from multiple backend systems.