Code Monkey home page Code Monkey logo

graphql.kit's Introduction

GraphQL for Clojure Ring Implementations

Motivation

For those who don't want to use pedestal, or merely prefer other web servers, this one's for you :cheers:.

This README is still a todo, there is a bit to document. But between what is here and the examples, is enough to work off of for the curious.

Quick Start

Install

Once there is more rigor (i.e. test suite) applied to the code in this repo, a clojars release will be supported. Until then, if you're a trail blazer:

land.bnert/graphql.kit {:git/url "https://github.com/bnert-land/graphql.kit"
                        :git/sha "..."}

Write Your App (Service/Easy)

If you're starting a new GraphQL project, and don't need to hook into an existing web stack, you can choose to use the prebuilt "service" interface:

(ns app.core
  (:require
    [graphql.kit :as kit]))


(defn -main [& _args]
  (kit/service!
    {:graphiql {:enabled?        true
                :url             "http://localhost:9109/graphql"
                :subscriptionUrl "ws://localhost:9109/graphql/subscribe"}
     :endpoints {:graphiql  "/graphiql"
                 :http      "/graphql"
                 :websocket "/graphql/subscribe"}
     ; There is a default "middleware chain". The main responsibility
     ; is to parse query params and json as well as negotiate resposne content/format.
     ;
     ; Therefore, the resulting middleware execution will look like:
     ;
     ; logger -> parsing ->  logger -> health-check -> root-handler
     ;
     :middleware {:prepend [(logger "PREPEND>")]
                  :append  [(logger "APPEND>") (health-check "/health")]}
     :scalars   {:UUID {:parse     #(when (string? %) (parse-uuid %))
                        :serialize str}}
     :resolvers {:query
                 ; Assuming each of these functions map to a resolver fn
                 {:Mutation/addDroid add-droid
                  :Mutation/addHuman add-human
                  :Query/droid       droid
                  :Query/droids      droids
                  :Query/human       human
                  :Query/humans      humans
                  :Query/hero        hero
                  :Query/heros       heros}
                 :subscription
                 {:Subscription/eventsevents}}
     :server    {:port 9109}
     ; Assuming an 'edn' schema is a resource on the classpath
     :schema    {:resource "graphql/schema/star-wars.edn"}}))

See the "easy" example, which implements the above.

Write Your App (Piecemeal)

The below app example is assuming you're definig your schema as edn as a resource:

(ns app.core
  (:require
    [aleph.http :as http]
    [graphql.kit.engines.lacinia :as kit.engine]
    [graphql.kit.loaders.edn :as kit.loader]
    [graphql.kit.servers.aleph.ws :as kit.ws]
    [graphql.kit.servers.ring.http :as kit.http]
    [graphql.kit.servers.ring.graphiql :as kit.graphiql]
    [ring.middleware.keyword-params :as mw.keyword-params]
    [ring.middleware.params :as mw.params]
    [ring.middleware.json :as mw.json]))

(def port 9109)

(def kit-config
  {:graphql.kit/engine (kit.engine/engine!)
   :graphql.kit/loader (kit.loader/loader!)

   ; Every config option w/o a :graphql.kit namespace is passed to
   ; the underlying graphql engine
   ;

   ; The `:options` are passed as options to Lacinia.
   ; You could use a manifold executor instead of the default
   ; thread pool.
   :options  {#_#_:executor (manifold.executor/execute-pool)}

   ; Schema can be
   ;   1. A map w/ `:resource` key. The path of the `:resource` key will be loaded using `:graphql.kit/loader`.
   ;   2. A map w/ `:path` key. The path will load a file using `:graphql.kit/loader`
   ;   3. A map w/o a `:path` or `:resource` key will be treated as a schema
   ;   4. A string, which signals that the schema is in SDL format.
   :schema   {:resource "graphql/schema.edn"}

   ; Resolvers have some extra structure, will go into more detail about the specifics of the lacinia
   ; engine at a later point in the docs.
   :resolvers
   {:query        {:Query/thing resolve-thing}
    :subscription {:Subscription/events subscribe-to-events}}

(def http-handler
  (kit.http/handler kit-config))

(def ws-handler
  (kit.ws/handler kit-config))

(def graphiql-handler
  (kit.graphiql/handler
    {:enabled? true ; defaults to false
     :url             "http://localhost:9109/graphql"
     :subscriptionUrl "ws://localhost:9109/graphql/subscribe"}))

(defn app [{:keys [request-method uri] :as req}]
  (case [request-method uri]
    [:get "/graphql"]
      (http-handler req)
    [:post "/graphql"]
      (http-handler req)
    [:get "/graphql/subscribe"]
      (ws-handler req)
    [:get "/graphiql"]
      (graphiql-handler req)
    #_default
      {:status 404}))

(defn -main [& args]
  (println (format "Starting server @ http://localhost:%s" port))
  (http/start-server
    (-> app
        (mw.json/wrap-json-response)
        (mw.keyword-params/wrap-keyword-params)
        (mw.params/wrap-params)
        (mw.json/wrap-json-params))
    {:port port}))

Examples

Concepts

Engines

In the context of graphql.kit, an "engine" is responsible for compiling a GraphQL schema and executing queries, subscriptions, and coorindating resolving values.

Current engine implementations:

Name Compliant w/ GraphQL Spec Compliant w/ Apollo Federation
Lacinia yes yes, only w/ SDL

In order to provide a well featured library, and given this library is in early stages, the focus is on integrating Lacinia.

Other Clojure or Java based GraphQL engines may be integrated, however, that is not the focus at this time.

There is an engine protocol, which when satistfied can plug into graphql.kit.

Loaders

A "loader" is responsible for loading a GraphQL schema. The current loaders are:

  • Default, which simply reads a file from either a file path or resource.
  • EDN, which reads file content from either the path or resource and parses the file into Clojure data structure.
  • Aero does everything that the EDN loader does, but also provides all the functionality exposed by Aero when reading configuration.
    • The Aero loader requires a peer dependency of juxt/aero aero is currently included. It will be "unbundled" when a first release is cut.

If none of the above are satisfactory, there is a loader protocol which can be implemented and plugged into graphql.kit.

Handlers

A "handler" is higher order function which takes a configuration map and produces a ring compliant handler.

Current handler implementaions:

References


Copyright (c) Brent Soles. All rights reserved.

See LICENSE for license information.

graphql.kit's People

Contributors

bnert avatar

Watchers

 avatar

graphql.kit's Issues

Subscription Values Not Being Resolved Correctly

To reproduce:

  1. $ cd into either examples/easy or examples/sdl-easy.
  2. Run $ clj -M -m server.core to start the server.
  3. Launch 2 instances GraphiQL in different tabs/windows
  4. In one window run:
subscription {
	events {
    kind
    data {
      ... on Human {
        id
        name
      }
      ... on Droid {
        id
        name
      }
      ... on Log {
        code
        message
      }
    }
  }
}
  1. In the other window run addHuman or addDroid.

At the point of 5, data should be shown in the subscription window w/ extra data fields.

SDL Parsing

Only the Lacinia engine is supported right now, and in order to get a good foundation, SDL parsing hasn't been tested nor supported.

With respect to Lacinia, defining schema w/ the SDL is better supported for federation, whereas, defining the schema w/ EDN is less so supported.

Example lacinia docs: https://lacinia.readthedocs.io/en/latest/federation/implementation.html
Example implementation: https://github.com/atomist/lacinia-apollo-federation/blob/master/src/com/atomist/lacinia_apollo_federation.clj

Finally, look into extending the lacinia schema to support federation.

Example impl: https://github.com/atomist/lacinia-apollo-federation/blob/master/src/com/atomist/lacinia_apollo_federation.clj

Add "Prebuilt" Services

For new projects, it may be advantageous to have an "easy mode", where a single namespace can be imported and project bootstrapped like so:

(ns app.core
  (:require
    [graphql.kit.services.aleph :as kit.service]))

(defn -main [& _args]
  (kit.service/service!
    {:graphiql?  true
     :port       8080
     :middleware {:append [], :prepend []}
     :schema     {:resource "graphql/schema.edn"}
     :resolvers  {:query        {:Query/heroes resolve-heroes}
                  :subscription {:Subscription/events stream-events}}}))

If a project doesn't need to be resty, or doesn't need to hook into a rest api, then why not make it easy to standup a graphql server w/o all the fuss?

Query Caching

A query cache interface should be defined, and some defaults implemented (ex. LRU).

GraphiQL Support

Add a ring handler which serves the GraphiQL app, w/ configuration for:

  • Query/Mutation Fetcher Endpoint
  • WebSocket Subscription Fetcher Endpoint

Test Suite

Me to myself:
where are they

Myself to me:
they're not there

Me to myself:
get it done

Myself to me:
thanks for your confidence

Precompile and Lazy Schema Compilation

A pattern I've landed on w/ multiple handlers is to have:

(def kit-config
  {:graphql.kit/engine "..."})

(def http-handler (kit.http/handler kit-config))
(def ws-handler (kit.ws/handler kit-config))
(def graphiql-handler (kit.graphiql/handler {:enabled? true, #_...})

In order to not perform extra work, it'd be good to allow for eager and/or lazy compilation:

(def kit-config
  (kit.util/precompile
    {:graphql.kit/engine "..."}))

; or for lazy, but single compilation. Basically would be: `(lazy (kit.util/precompile ...))`.
#_(def kit-config
  (kit.util/lazy-compile
    {:graphql.kit/engine "..."}))

(def http-handler (kit.http/handler kit-config))
(def ws-handler (kit.ws/handler kit-config))
(def graphiql-handler (kit.graphiql/handler {:enabled? true, #_...})

Docs

Once the library is in an alpha state, work on docs for use cases and provide example projects in repo.

Investigate GraphQL Java As Possible Engine

GraphQL Java docs site: https://www.graphql-java.com/

I think it is possible to use the GraphQL Java library.

Don't want to spend a ton of time on this, but it may be useful to provide GraphQL Java as an alternative to lacinia, given it is mature and has some interesting implementation details around execution and batching.

Resolvers/subscriptions, if possible, should have the same arity and meaning as lacinia handlers.

Unify "Engine" Protocols

Currently an "Engine" is only concerned with execution. Given execution and parsing/compiling go hand in hand with most (if not all) GraphQL implementations, it is better to have a unified protocol, rather than an "Engine" and "Compiler" protocol.

Additionally, the graphql.kit.runtime seems to be unnecessary. Loaders and engine can be passed to individual handlers, or provided via a unified context, like so:

; example with reitit-esque routes
(graphql.kit/context {:engine 'graphql.kit.engines/lacinia
                      :loader 'graphql.kit.loaders/aero
                      :resolvers {:Query/thing         app.resolvers/thing
                                  :Subscription/events app.resolvers/events}
                      :schema {:resource "graphql/schema.edn"}}
  [["/graphql" {:get {:handler (graphql.kit.ring.http/handler)
                :post {:handler (graphql.kit.ring.http/handler)}
    "/ws/graphql" {:get {:handler (grahql.kit.ring.ws/handler)}}]])

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.