GraphQL API in ClojureGraphQL API in Clojure
lagénorhynquelagénorhynque
(defprofile lagénorhynque
:aliases [ ]
:languages [Clojure Common-Lisp Scheme Haskell
English français]
:interests [programming Love-Live!
language-learning/linguistics law mathematics]
:committing [github.com/lagenorhynque/duct.module.pedestal]
:contributing [github.com/japan-clojurians/clojure-site-ja])
1. GraphQL
GraphQL query
GraphQL schema
2. API implementation
project structure
API server
GraphQL implementation
Clojure Lacinia GraphQL API
exampleexample
: AqoursQL, an example
GraphQL API based on Lacinia-Pedestal & Duct
lagenorhynque/aqoursql
GraphQLGraphQL
venia & edn
GraphQL queryGraphQL query
venia
query:query: member_by_idmember_by_id
{
member_by_id(id: 1) {
id
name
organization_id
organization_name
}
}
response (JSON)response (JSON)
{
"data": {
"member_by_id": {
"id": 1,
"name": " ",
"organization_id": 1,
"organization_name": " "
}
}
}
GraphiQLGraphiQL
Clojure REPLClojure REPL
dev> (q #:venia{:queries [[:member_by_id {:id 1}
[:id
:name
:organization_id
:organization_name]]]})
{:data
{:member_by_id
{:id 1, :name " ", :organization_id 1,
:organization_name " "}}}
utility functionutility function qq
(defn q
([query] (q query nil))
([query variables]
(lacinia/execute (:aqoursql.graphql/schema system)
(venia/graphql-query query)
variables
{:db (:duct.database.sql/hikaricp system)})))
venia.core/graphql­query
com.walmartlabs.lacinia/execute
query:query: songssongs
{
songs {
name
artist {
name
members {
name
}
}
}
}
response (JSON)response (JSON)
{
"data": {
"songs": [
{
"name": " ",
"artist": {
"name": "Aqours",
"members": [
{
"name": " "
}, ...
]
}
}, ...
]
}
GraphiQLGraphiQL
Clojure REPLClojure REPL
dev> (q #:venia{:queries [[:songs
[:name
[:artist
[:name
[:members
[:name]]]]]]]})
{:data
{:songs
({:name " ",
:artist
{:name "Aqours",
:members
({:name " "}
...
GraphQL schemaGraphQL schema
edn
object de nition:object de nition: MemberMember
""" """
type Member {
""" ID"""
id: Int!
""" """
name: String!
""" ID"""
organization_id: Int!
""" """
organization_name: String!
}
resources/aqoursql/graphql-schema.edn
{:objects
{:Member
{:description " "
:fields
{:id {:type (non-null Int)
:description " ID"}
:name {:type (non-null String)
:description " "}
:organization_id {:type (non-null Int)
:description " ID"}
:organization_name {:type (non-null String)
:description " "}}}}}
object de nition:object de nition: SongSong
""" """
type Song {
""" ID"""
id: Int!
""" """
name: String!
"""" ID""
artist_id: Int!
""" """
artist: Artist!
""" (YYYY-MM-DD)"""
release_date: String!
}
resources/aqoursql/graphql-schema.edn
{:objects
{:Song
{:description " "
:fields
{:id {:type (non-null Int)
:description " ID"}
:name {:type (non-null String)
:description " "}
:artist_id {:type (non-null Int)
:description " ID"}
:artist {:type (non-null :Artist)
:description " "}
:release_date {:type (non-null String)
:description " (YYYY-MM-DD)"}}}}}
query de nition:query de nition: member_by_idmember_by_id
type Query {
"""ID """
member_by_id(
" ID"
id: Int!
): Member
}
resources/aqoursql/graphql-schema.edn
{:queries
{:member_by_id
{:type :Member
:description "ID "
:args
{:id {:type (non-null Int)
:description " ID"}}
:resolve :query/member-by-id}}}
query de nition:query de nition: songssongs
type Query {
""" """
songs(
" ( )"
name: String
): [Song]
}
{:queries
{:songs
{:type (list :Song)
:description " "
:args
{:name {:type String
:description " ( )"}}
:resolve :query/songs}}}
API implementationAPI implementation
Duct + Pedestal + Lacinia
project structureproject structure
Duct
Clojure Duct
resources/aqoursql/con g.edn
{:duct.profile/base
{:duct.core/project-ns aqoursql
:duct.server/pedestal { ... }
:aqoursql.graphql/schema {}
:aqoursql.graphql/service { ... }}
:duct.profile/dev #duct/include "dev"
:duct.profile/test #duct/include "test"
:duct.profile/local #duct/include "local"
:duct.profile/prod {}
:duct.module/logging {}
:duct.module/sql { ... }
:duct.module/pedestal {}}
API serverAPI server
Pedestal
duct.module.pedestal
Clojure Pedestal
resources/aqoursql/con g.edn
{:duct.profile/base
{:duct.core/project-ns aqoursql
:duct.server/pedestal
{:base-service #ig/ref :aqoursql.graphql/service
:service #:io.pedestal.http{:join? true
:host #duct/env "SERVER_HOST"
:port #duct/env ["SERVER_PORT" Int
:or 8888]}}
... }
...
:duct.module/pedestal {}}
GraphQL implementationGraphQL implementation
Lacinia
Lacinia-Pedestal
resources/aqoursql/con g.edn
{:duct.profile/base
{:duct.core/project-ns aqoursql
...
:aqoursql.graphql/schema {}
:aqoursql.graphql/service
{:schema #ig/ref :aqoursql.graphql/schema
:options {:graphiql true
:app-context {:db #ig/ref :duct.database/sql}
:env :prod}}}
... }
src/aqoursql/graphql.clj
(defmethod ig/init-key ::schema
[_ _]
(-> (io/resource "aqoursql/graphql-schema.edn")
slurp
edn/read-string
(util/attach-resolvers resolver-map)
schema/compile))
(defmethod ig/init-key ::service
[_ {:keys [schema options]}]
(lacinia/service-map schema options))
resolver (function)resolver (function)
src/aqoursql/graphql.clj
(def resolver-map
{:query/artist-by-id artists/fetch-artist-by-id
:query/artists artists/list-artists
:query/member-by-id members/fetch-member-by-id
:query/members members/list-members
:query/song-by-id songs/fetch-song-by-id
:query/songs songs/list-songs})
resolver function spec
src/aqoursql/resolver/members.clj
(defn fetch-member-by-id [{:keys [db]} {:keys [id]} _]
(db.member/find-member-by-id db id))
(s/fdef resolver
:args (s/cat :app-context map?
:arguments (s/nilable map?)
:resovled-value (s/nilable map?)))
boundary (DB)boundary (DB)
src/aqoursql/boundary/db/member.clj
(s/def ::id nat-int?)
(s/def ::name string?)
(s/def ::organization_id ::organization/id)
(s/def ::organization_name ::organization/name)
(s/def ::artist_id ::artist/id)
(s/def ::artist_ids (s/coll-of ::artist/id))
(s/def ::member
(s/keys :req-un [::id
::name
::organization_id]
:opt-un [::organization_name
::artist_id]))
(s/fdef find-member-by-id
:args (s/cat :db ::db/db
:id ::id)
:ret (s/nilable ::member))
...
(defprotocol Member
(find-member-by-id [db id])
... )
dev> (aqoursql.boundary.db.member/find-member-by-id
(:duct.database.sql/hikaricp system) 1)
{:id 1, :name " ", :organization_id 1,
:organization_name " "}
dev> (aqoursql.resolver.members/fetch-member-by-id
{:db (:duct.database.sql/hikaricp system)}
{:id 1} nil)
{:id 1, :name " ", :organization_id 1,
:organization_name " "}
dev> (q #:venia{:queries [[:member_by_id {:id 1}
[:name
:organization_name]]]})
{:data {:member_by_id {:name " ",
:organization_name " "}}}
Further ReadingFurther Reading
LaciniaLacinia
: Expose Lacinia GraphQL as
Pedestal endpoints
example:
Lacinia
Lacinia-Pedestal
Clojure Lacinia GraphQL API
lagenorhynque/aqoursql
Clojure GraphQL
Lacinia Tips
DuctDuct
: Duct module for Pedestal
Duct
duct.module.pedestal
Clojure Duct
PedestalPedestal
Pedestal
Clojure Pedestal
GraphQLGraphQL
: Clojure(Script) graphql query generationvenia
GraphQL | A query language for your API
How to GraphQL - The Fullstack Tutorial for
GraphQL
GraphQL ─ REST API
Learning GraphQL

GraphQL API in Clojure