Lacinia Pedestal

Working from the REPL is important, but ultimately GraphQL exists to provide a web-based API. Fortunately, it is very easy to get your Lacinia application up on the web, on top of the Pedestal web tier, using the lacinia-pedestal library.

In addition, for free, we get GraphQL’s own REPL: GraphiQL.

Add Dependencies

deps.edn
{:paths ["src" "resources"]
 :deps  {org.clojure/clojure              {:mvn/version "1.11.1"}
         com.walmartlabs/lacinia          {:mvn/version "1.2-alpha-4"}
         com.walmartlabs/lacinia-pedestal {:mvn/version "1.1"}
         io.aviso/logging                 {:mvn/version "1.0"}}
 :aliases
 {:run-m {:main-opts ["-m" "my.clojure-game-geek"]}
  :run-x {:ns-default my.clojure-game-geek
          :exec-fn    greet
          :exec-args  {:name "Clojure"}}
  :build {:deps       {io.github.seancorfield/build-clj
                       {:git/tag   "v0.8.2" :git/sha "0ffdb4c"
                        ;; since we're building an app uberjar, we do not
                        ;; need deps-deploy for clojars.org deployment:
                        :deps/root "slim"}}
          :ns-default build}
  :dev   {:extra-paths ["dev-resources"]}
  :test  {:extra-paths ["test"]
          :extra-deps  {org.clojure/test.check {:mvn/version "1.1.1"}
                        io.github.cognitect-labs/test-runner
                        {:git/tag "v0.5.0" :git/sha "48c3c67"}}}}}

We’ve added two libraries: lacinia-pedestal and io.aviso/logging.

The former brings in quite a few dependencies, including Pedestal, and the underlying Jetty layer that Pedestal builds upon.

The io.aviso/logging library sets up Logback as the logging library.

Clojure and Java are both rich with web and logging frameworks; Pedestal and Logback are simply particular choices that we’ve made and prefer; many other people are using Lacinia on the web without using Logback or Pedestal.

Some Configuration

For best results, we can configure Logback; this keeps startup and request handling from being very chatty:

dev-resources/logback-test.xml
<configuration scan="true" scanPeriod="1 seconds">

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%-5level %logger - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="warn">
    <appender-ref ref="STDOUT"/>
  </root>

</configuration>

This configuration hides log events below the warning level (that is, debug and info events). If any warnings or errors do occur, minimal output is sent to the console.

A logback-test.xml takes precendence over the production logback.xml configuration we will eventually supply.

User Namespace

We’ll add more scaffolding to the user namespace, to make it possible to start and stop the Pedestal server.

dev-resources/user.clj
(ns user
  (:require [my.clojure-game-geek.schema :as s]
            [com.walmartlabs.lacinia :as lacinia]
            [com.walmartlabs.lacinia.pedestal2 :as lp]
            [io.pedestal.http :as http]
            [clojure.java.browse :refer [browse-url]]
            [clojure.walk :as walk])
  (:import (clojure.lang IPersistentMap)))

(def schema (s/load-schema))

(defn simplify
  "Converts all ordered maps nested within the map into standard hash maps, and
   sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems."
  [m]
  (walk/postwalk
    (fn [node]
      (cond
        (instance? IPersistentMap node)
        (into {} node)

        (seq? node)
        (vec node)

        :else
        node))
    m))

(defn q
  [query-string]
  (-> (lacinia/execute schema query-string nil nil)
      simplify))

(defonce server nil)

(defn start-server
  [_]
  (let [server (-> (lp/default-service schema nil)
                   http/create-server
                   http/start)]
    (browse-url "http://localhost:8888/ide")
    server))

(defn stop-server
  [server]
  (http/stop server)
  nil)

(defn start
  []
  (alter-var-root #'server start-server)
  :started)

(defn stop
  []
  (alter-var-root #'server stop-server)
  :stopped)

This new code is almost entirely boilerplate for Pedestal and for Lacinia-Pedestal. The core function is com.walmartlabs.lacinia.pedestal2/default-service [1] which is passed the compiled schema and a map of options, and returns a Pedestal service map which is then used to create the Pedestal server.

By default, incoming GraphQL POST requests are handled at the /api path. The default port is 8888. We’ll get to the details later.

The /ide path (which is opened at startup), and related JavaScript and CSS resources, can only be accessed when GraphiQL is enabled.

Starting The Server

With the above scaffolding in place, it is just a matter of starting the REPL and evaluating (start).

At this point, your web browser should open to the GraphiQL application:

../_images/graphiql-initial.png

Tip

It’s really worth following along with this section, especially if you haven’t played with GraphiQL before. GraphiQL assists you with formatting, provides pop-up help, flags errors in your query, and supplies automatic input completion. It can even pretty print your query. It makes for quite the demo!

Running Queries

We can now type a query into the large text area on the left and then click the right arrow button (or type Command+Enter), and see the server response as pretty-printed JSON on the right:

../_images/graphiql-basic-query.png

Notice that the URL bar in the browser has updated: it contains the full query string. This means that you can bookmark a query you like for later (though it’s easier to access prior queries using the the History button).

Importantly, you can copy that URL and provide it to other developers. They can start up the application on their workstations and see exactly what you see, a real boon for describing and diagnosing problems.

This approach works even better when you keep a GraphQL server running on a shared staging server. On split [2] teams, the developers creating the application can easily explore the interface exposed by the GraphQL server, even before writing their first line of client-side code.

Trust me, they love that.

You’ll notice that the returned map is in JSON format, not EDN, and that it includes a lot more information in the extensions key. This is optional tracing information, where Lacinia identifies how it spent time processing the request. This is an example of something that’s automatic when using default-service that you’ll definitely want to turn off in production.

Documentation Browser

The < Docs button on the right opens the documentation browser:

../_images/graphiql-doc-browser.png

The documentation browser is invaluable: it allows you to navigate around your schema, drilling down to objects, fields, and types to see a summary of each declaration, as well as documentation - those :description values we added way back at the beginning.

Take some time to learn what GraphiQL can do for you.

Summary

It takes very little effort, just a dependency change and a little boilerplate code, to expose our little application to the web, and along the way, we gain access to the powerful GraphiQL IDE.

Next up, we’ll look into reorganization our code for later growth by adding a layer of components atop our code.

[1]Why pedestal2? The initial version of lacinia-pedestal had a slightly different approach to setting up Pedestal that proved to be problematic, it also supported some outdated ideas about how to process incoming requests. For compatibility, the original namespace, com.walmartlabs.lacinia.pedestal was left functionally s-is, but a new namespace, pedestal2 was created to address the concerns.
[2]That is, where one team or set of developers just does the user interface, and the other team just does the server side (including Lacinia). Part of the value proposition for GraphQL is how clean and uniform this split can be.