All Projects → retro → Graphql Builder

retro / Graphql Builder

Licence: mit
GraphQL client library for Clojure and ClojureScript

Programming Languages

clojure
4091 projects
clojurescript
191 projects

Labels

Projects that are alternatives of or similar to Graphql Builder

Graphql Jpa
JPA Implementation of GraphQL (builds on graphql-java)
Stars: ✭ 156 (-4.29%)
Mutual labels:  graphql
Repo Remover
A web app that helps you archive and delete old/unused repos, quickly and easily.
Stars: ✭ 160 (-1.84%)
Mutual labels:  graphql
React Admin Low Code
react-admin (via ra-data-hasura-graphql provider) + hasura = :)
Stars: ✭ 161 (-1.23%)
Mutual labels:  graphql
Babel Plugin Graphql Tag
Compiles GraphQL tagged template strings using graphql-tag.
Stars: ✭ 156 (-4.29%)
Mutual labels:  graphql
Next Apollo Auth
Authentication Boilerplate with Next.js and Apollo GraphQL
Stars: ✭ 159 (-2.45%)
Mutual labels:  graphql
Graphql Rails Blog
Blog App built with Rails 5, React and GraphQL
Stars: ✭ 160 (-1.84%)
Mutual labels:  graphql
Countries
🌎 Public GraphQL API for information about countries
Stars: ✭ 156 (-4.29%)
Mutual labels:  graphql
Vendure
A headless GraphQL ecommerce framework for the modern web
Stars: ✭ 2,961 (+1716.56%)
Mutual labels:  graphql
Ferry
Stream-based strongly typed GraphQL client for Dart
Stars: ✭ 160 (-1.84%)
Mutual labels:  graphql
Frisky
🍿 Open Source GraphQL API for Online Shows
Stars: ✭ 161 (-1.23%)
Mutual labels:  graphql
Lacinia Pedestal
Expose Lacinia GraphQL as Pedestal endpoints
Stars: ✭ 157 (-3.68%)
Mutual labels:  graphql
Portara
Portara directive is a rate limiter / throttler for GraphQL
Stars: ✭ 158 (-3.07%)
Mutual labels:  graphql
Dataloader Php
DataLoaderPhp is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.
Stars: ✭ 160 (-1.84%)
Mutual labels:  graphql
Combine Pagination
A JavaScript library for paginating data from multiple sources 🦑
Stars: ✭ 157 (-3.68%)
Mutual labels:  graphql
Cms Mobile
A flutter project for amfoss cms
Stars: ✭ 162 (-0.61%)
Mutual labels:  graphql
Reactconfbr
Public infos and issues about React Conf Brasil organization
Stars: ✭ 156 (-4.29%)
Mutual labels:  graphql
Webtau
Webtau (short for web test automation) is a testing API, command line tool and a framework to write unit, integration and end-to-end tests. Test across REST-API, Graph QL, Browser, Database, CLI and Business Logic with consistent set of matchers and concepts. REPL mode speeds-up tests development. Rich reporting cuts down investigation time.
Stars: ✭ 156 (-4.29%)
Mutual labels:  graphql
Examples
Examples of Mock Service Worker usage with various frameworks and libraries.
Stars: ✭ 163 (+0%)
Mutual labels:  graphql
Wp Graphql Gutenberg
Query gutenberg blocks with wp-graphql
Stars: ✭ 158 (-3.07%)
Mutual labels:  graphql
Pop
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder
Stars: ✭ 160 (-1.84%)
Mutual labels:  graphql

graphql-builder

Clojars Project

GraphQL client library for Clojure and ClojureScript.

Why

Writing GraphQL queries in the frontend applications is not straight forward. In JavaScript world it is common to see GraphQL queries written as inline strings inside the application code:

client.query(`
    {
      allFilms {
        films {
          title
        }
      }
    }
`).then(result => {
    console.log(result.allFilms);
});

Although it gets the work done, it is easy to make mistakes without syntax coloring, and any validation of the query syntax is impossible. In ClojureScript this approach looks even worse:

(def inline-fragment-source "
query LoadStarships($starshipCount: Int!) {
  allStarships(first: $starshipCount) {
    edges {
      node {
        id
        name
        model
        costInCredits
        pilotConnection {
          edges {
            node {
              ...pilotFragment
            }
          }
        }
      }
    }
  }
}
fragment pilotFragment on Person {
  name
  homeworld { name }
}
")

I wanted something similar to the HugSQL library which would allow me to keep the queries inside the .graphql files while being able to easily use them from my frontend code.

Approach

This library uses the parser from the alumbra library to parse the .graphql files and then implements the GraphQL code generation on top of the output format.

Parsing and regenerating allows for some (automatic) advanced features:

  • Resolving dependencies between queries and fragments
  • Fragment inlining
  • Query namespacing (with prefixes)
  • Query composition - combine multiple queries into one query
  • Mutation composition – combine multiple mutations into one query
  • Subscriptions

API

Loading GraphQL files:

(ns graphql-test
    (:require
        [graphql-builder.parser :refer-macros [defgraphql]]
        [graphql-builder.core :as core]))

(defgraphql graphql-queries "file1.graphql" "file2.graphql")
(def query-map (core/query-map graphql-queries))

If the GraphQL file contained the following:

query LoadStarships($starshipCount: Int!) {
  allStarships(first: $starshipCount) {
    edges {
      node {
        id
        name
        model
        costInCredits
        pilotConnection {
          edges {
            node {
              ...pilotFragment
            }
          }
        }
      }
    }
  }
}
fragment pilotFragment on Person {
  name
  homeworld {
    name
  }
}

you could access the LoadStarships function like this:

(def load-starships-query (get-in query-map [:query :load-starships]))

The returned function accepts one argument: query variables (if needed). Calling the function will return the following:

(load-starships-query {})

;; return value from the load-starships-query function
{:graphql {:query "GraphQL Query string"
           :variables {...} ;; variables passed to the load-starships-query function
           :operationName "..." ;; Name of the query
           }
 :unpack (fn [data])} ;; function used to unpack the data returned from the GraphQL query

The returned GraphQL Query will contain all of the referenced fragments.

Calling the GraphQL API is out of the scope of this library, but it can be easily implemented with any of the ClojureScript AJAX Libraries.

Fragment Inlining

graphql-builder can inline the referenced fragments inside the query. To inline the fragments, pass the {:inline-fragments true} config to the query-map function:

(ns graphql-test
    (:require
        [graphql-builder.parser :refer-macros [defgraphql]]
        [graphql-builder.core :as core]))

(defgraphql graphq-queries "file1.graphql" "file2.graphql")
(def query-map (core/query-map graphql-queries {:inline-fragments true}))

If you called the load-starships-query function again, the returned GraphQL string would look like this:

query LoadStarships($starshipCount: Int!) {
  allStarships(first: $starshipCount) {
    edges {
      node {
        id
        name
        model
        costInCredits
        pilotConnection {
          edges {
            node {
              name
              homeworld {
                name
              }
            }
          }
        }
      }
    }
  }
}

Query prefixing (namespacing)

grapqhl-builder can "namespace" the GraphQL query. To namespace the query, pass the {:prefix "NameSpace"} config to the query-map function:

(ns graphql-test
    (:require
        [graphql-builder.parser :refer-macros [defgraphql]]
        [graphql-builder.core :as core]))

(defgraphql graphq-queries "file1.graphql" "file2.graphql")
(def query-map (core/query-map graphql-queries {:prefix "NameSpace"}))

If you called the load-starships-query function again, the returned GraphQL string would look like this:

query LoadStarships($NameSpace__starshipCount: Int!) {
  NameSpace__allStarships: allStarships(first: $NameSpace__starshipCount) {
    edges {
      node {
        id
        name
        model
        costInCredits
        pilotConnection {
          edges {
            node {
              ...pilotFragment
            }
          }
        }
      }
    }
  }
}

If the referenced fragments use variables, you must inline them to get the correct behavior.

Query Composition

Fragment inlining and namespacing are cool features on their own, but together they unlock the possibility to compose the queries.

Let's say that you have GraphQL file that contains the following query:

query Hero($episode: String!) {
  hero(episode: $episode) {
    name
  }
}

and you want to call the query for multiple episodes. Usually you would create another query for this:

query {
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}

but, with graphql-builder you can compose this query from the application code:

 (def composed-query
   (core/composed-query graphql-queries {:jedi-hero "Hero" :empire-hero "Hero"}))

Now you can call this function and it will handle namespacing both of the query and the variables automatically:

(composed-query {:empire-hero {:episode "EMPIRE"}} {:jedi-hero {:episode "JEDI"}})

This function will return the same object like the functions created by the query-map:

;; return value from the load-starships-query function
{:graphql {:query "GraphQL Query string"
           :variables {...} ;; variables passed to the load-starships-query function
           :operationName "..." ;; Name of the query
           }
 :unpack (fn [data])} ;; function used to unpack the data returned from the GraphQL query

In this case the GraphQL query string will look like this:

query ComposedQuery($JediHero__episode: String!, $EmpireHero__episode: String!) {
  JediHero__hero: hero(episode: $JediHero__episode) {
    name
  }
  EmpireHero__hero: hero(episode: $EmpireHero__episode) {
    name
  }
}

When you receive the result, you can use the returned unpack function to unpack them.

(unpack {"EmpireHero__hero" {:name "Foo"} "JediHero__hero" {:name "Bar"}})

;; This will return the unpacked results:

{:empire-hero {"hero" "Foo"}
 :jedi-hero {"hero" "Bar"}}

Mutation Composition

You can also compose mutations in the same manner you can compose queries. The only difference is that the mutations might depend on each other, so the ordering of those mutations might be relevant.

This can be achieved by providing mutation keys that are sorted by the sort method in Clojure.

Assuming you have a mutation

mutation AddStarship($name: String!){
    addStarship(name: $name){
        id
   }
}

You can compose multiple mutations together using the composed-mutation function:

(def composed-mutation
   (core/composed-mutation graphql-queries {:add-starship-1 "AddStarship"
                                            :add-starship-2 "AddStarship"}))

When you execute the result, you get back the same structure as with composed queries, providing unpack function to parse the result from the server.

(let [{unpack :unpack} (composed-mutation)]
  (unpack {"AddStarship1__name" "starship-1"
           "AddStarship2__name" "starship-2"}})

returns

{:add-starship-1 {"name" "starship-1"}
 :add-starship-2 {"name" "starship-2"}}

Tests

License

Copyright Mihael Konjevic, Tibor Kranjcec ([email protected]) © 2020

Distributed under the MIT license.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].