1
1
= JVM Library to translate GraphQL queries and mutations to Neo4j's Cypher
2
2
3
- This is an early stage alpha implementation written in Kotlin.
3
+ This is a beta GraphQL transpiler written in Kotlin.
4
4
5
5
== How does it work
6
6
@@ -13,28 +13,30 @@ Those Cypher queries can then executed, e.g via the Neo4j-Java-Driver (or other
13
13
14
14
The request, result and error handling is not part of this library, but we provide demo programs on how to use it in different languages.
15
15
16
- NOTE: All the supported features are listed below, detailed docs will be added.
16
+ NOTE: All the <<features| supported features>> are listed and explained below, more detailed docs will be added in time .
17
17
18
18
== FAQ
19
19
20
20
=== How does this relate to the other neo4j graphql libraries?
21
21
22
- Similar to `neo4j-graphql-js` this library focusses on query translation, just for the *JVM* instead of Node.js.
23
- It does not provide a server or other facilities but is meant to be used as a dependency included for a single purpose.
22
+ Similar to `neo4j-graphql-js` this library focuses on query translation, just for the *JVM* instead of Node.js.
23
+ It does not provide a server (except as examples) or other facilities but is meant to be used as a dependency included for a single purpose.
24
24
25
25
If this library is feature complete we plan to replace the code in the current Neo4j server plugin `neo4j-graphql` with a single call to this library.
26
26
The server plugin would still handle request-response and error-handling, and perhaps some schema management but be slimmed down to a tiny piece.
27
27
28
28
=== How does this related to graphql-java
29
29
30
- This library uses GraphQL Java under the hood for parsing of schema and queries, and managing the GraphQL state and context.
31
- But not for nested field resolvers or data fetching.
30
+ This library uses `graphql-java` under the hood for parsing of schema and queries, and managing the GraphQL state and context.
31
+ But * not for nested field resolvers or data fetching* .
32
32
33
- If you wanted to you could combine graphql-java resolvers with this library to have a part of your schema handled by Neo4j.
33
+ If you wanted, you could combine `graphql-java` resolvers with this library to have a part of your schema handled by Neo4j.
34
+
35
+ Thanks a lot to the maintainers of `graphql-java` for the awesome library.
34
36
35
37
== Usage
36
38
37
- You can use the library as dependency: `org.neo4j:neo4j-graphql-java:1.0.0-M01 ` in any JVM program.
39
+ You can use the library as dependency: `org.neo4j:neo4j-graphql-java:1.0.0-M02 ` in any JVM program.
38
40
39
41
The basic usage should be:
40
42
@@ -43,9 +45,10 @@ The basic usage should be:
43
45
val schema =
44
46
"""
45
47
type Person {
46
- name: String
48
+ name: ID!
47
49
age: Int
48
50
}
51
+ # Optional if you use generated queries
49
52
type Query {
50
53
person : [Person]
51
54
personByName(name:String) : Person
@@ -56,14 +59,19 @@ val query = """ { p:personByName(name:"Joe") { age } } """
56
59
val schema = SchemaBuilder.buildSchema(idl)
57
60
val (cypher, params) = Translator(schema).translate(query, params)
58
61
62
+ // generated Cypher
59
63
cypher == "MATCH (p:Person) WHERE p.name = $pName RETURN p {.age} as p"
60
64
----
61
65
66
+ You find more usage examples in the link:src/test/resources[TCK scripts].
67
+
62
68
== Demo
63
69
64
70
Here is a minimalistic example in Groovy using the Neo4j-Java driver and Spark-Java as webserver.
65
71
It is running against a Neo4j instance at `bolt://localhost` (username: `neo4j`, password: `password`) containing the `:play movies` graph.
66
72
73
+ (You can also use a link:src/test/kotlin/GraphQLServer.kt[Kotlin based server example].)
74
+
67
75
[source,groovy]
68
76
----
69
77
// Simplistic GraphQL Server using SparkJava
@@ -82,12 +90,12 @@ import org.neo4j.driver.v1.*
82
90
83
91
schema = """
84
92
type Person {
85
- name: String
93
+ name: ID!
86
94
born: Int
87
95
actedIn: [Movie] @relation(name:"ACTED_IN")
88
96
}
89
97
type Movie {
90
- title: String
98
+ title: ID!
91
99
released: Int
92
100
tagline: String
93
101
}
@@ -123,12 +131,12 @@ It uses a schema of:
123
131
[source,graphql]
124
132
----
125
133
type Person {
126
- name: String
134
+ name: ID!
127
135
born: Int
128
136
actedIn: [Movie] @relation(name:"ACTED_IN")
129
137
}
130
138
type Movie {
131
- title: String
139
+ title: ID!
132
140
released: Int
133
141
tagline: String
134
142
}
@@ -160,8 +168,11 @@ You can also test it with `curl`
160
168
curl -XPOST http://localhost:4567/graphql -d'{"query":"{person {name}}"}'
161
169
----
162
170
171
+ This example doesn't handle introspection queries but the one in the test directory does.
172
+
163
173
== Advanced Queries
164
174
175
+ .Filter, Sorting, Paging support
165
176
----
166
177
{
167
178
person(filter: {name_starts_with: "L"}, orderBy: "born_asc", first: 5, offset: 2) {
@@ -187,6 +198,7 @@ curl -XPOST http://localhost:4567/graphql -d'{"query":"{person {name}}"}'
187
198
}
188
199
----
189
200
201
+ [[features]]
190
202
== Features
191
203
192
204
=== Current
@@ -195,15 +207,17 @@ curl -XPOST http://localhost:4567/graphql -d'{"query":"{person {name}}"}'
195
207
* resolve query fields via result types
196
208
* handle arguments as equality comparisons for top level and nested fields
197
209
* handle relationships via @relation directive on schema fields
210
+ * @relation directive on types for rich relationships (from, to fields for start & end node)
198
211
* handle first, offset arguments
199
212
* argument types: string, int, float, array
200
- * parameter support
201
- * parametrization
213
+ * request parameter support
214
+ * parametrization for cypher query
202
215
* aliases
203
216
* inline and named fragments
204
- * auto-generate queries
205
- * @cypher for fields
206
- * auto-generate mutations
217
+ * auto-generate query fields for all objects
218
+ * @cypher directive for fields to compute field values, support arguments
219
+ * auto-generate mutation fields for all objects to create, update, delete
220
+ * @cypher directive for top level queries and mutations, supports arguments
207
221
208
222
=== Next
209
223
@@ -219,8 +233,12 @@ curl -XPOST http://localhost:4567/graphql -d'{"query":"{person {name}}"}'
219
233
220
234
=== Parse SDL schema
221
235
222
- Currently schemas with object types, enums and Query types are parsed and handled.
236
+ Currently schemas with object types, enums, fragments and Query types are parsed and handled.
237
+ We support @relation directives on fields and types for rich relationships
238
+ We support @cypher directives on fields and top-level query and mutation fields.
239
+ The configurable augmentation auto-generates queries and mutations (create,update,delete) for all types.
223
240
It supports the built-in scalars for GraphQL.
241
+ For arguments we support input types in many places and filters as known from GraphCool/Prisma.
224
242
225
243
=== Resolve query Fields via Result Types
226
244
@@ -413,6 +431,7 @@ person(name:"Keanu Reeves") {
413
431
}
414
432
----
415
433
434
+ [[filters]]
416
435
=== Filters
417
436
418
437
Filters are a powerful way of selecting a subset of data.
@@ -442,4 +461,153 @@ You can also apply nested filter on relations, which use suffixes like `("",not,
442
461
}
443
462
----
444
463
445
- NOTE: Those nested input types are not yet generated, we use leniency in the parser.
464
+ NOTE: Those nested input types are not yet generated, we use leniency in the parser.
465
+
466
+ === Inline and Named Fragments
467
+
468
+ We support inline and named fragments according to the GraphQL spec.
469
+ Most of this is resolved on the parser/query side.
470
+
471
+ .Named Fragment
472
+ [source,graphql]
473
+ ----
474
+ fragment details on Person { name, email, dob }
475
+ query {
476
+ person {
477
+ ...details
478
+ }
479
+ }
480
+ ----
481
+
482
+ .Inline Fragment
483
+ [source,graphql]
484
+ ----
485
+ query {
486
+ person {
487
+ ... on Person { name, email, dob }
488
+ }
489
+ }
490
+ ----
491
+
492
+
493
+ === @cypher Directives
494
+
495
+ With `@cypher` directives you can add the power of Cypher to your GraphQL API.
496
+ It allows you, without code to compute field values using complex queries.
497
+ You can also write your own, custom top-level queries and mutations using Cypher.
498
+
499
+ Arguments on the field are passed to the Cypher statement as parameters.
500
+ Input types are supported, they appear as `Map` type in your Cypher statement.
501
+
502
+ NOTE: Those Cypher directive queries are only included in the generated Cypher statement if the field or query is included in the GraphQL query.
503
+
504
+ ==== On Fields
505
+
506
+ .@cypher directive on a field
507
+ [source,graphql]
508
+ ----
509
+ type Movie {
510
+ title: String
511
+ released: Int
512
+ similar(limit:Int=10): [Movie] @cypher(statement:"
513
+ MATCH (this)-->(:Genre)<--(sim)
514
+ WITH sim, count(*) as c ORDER BY c DESC LIMIT $limit
515
+ RETURN sim")
516
+ }
517
+ ----
518
+
519
+ Here the `this` variable is bound to the current movie and you can use it to navigate the graph and collect data.
520
+ The `limit` variable is passed to the query as parameter.
521
+
522
+ ==== On Queries
523
+
524
+ Similarly you can use the `@cypher` directive with a top level query.
525
+
526
+ .@cypher directive on query
527
+ [source,graphql]
528
+ ----
529
+ type Query {
530
+ person(name:String) Person @cypher("MATCH (p:Person) WHERE p.name = $name RETURN p")
531
+ }
532
+ ----
533
+
534
+ Of course you can also return arrays from your query, the statements on query fields should be read-only queries.
535
+
536
+ ==== On Mutations
537
+
538
+ You can do the same for mutations, just with updating Cypher statements.
539
+
540
+ .@cypher directive on mutation
541
+ [source,graphql]
542
+ ----
543
+ type Mutation {
544
+ createPerson(name:String, age:Int) Person @cypher("CREATE (p:Person) SET p.name = $name, p.age = $age RETURN p")
545
+ }
546
+ ----
547
+
548
+ You can use more complex statements for creating these entities or even subgraphs.
549
+
550
+ NOTE: The common CRUD mutations and queries are auto-generated, see below.
551
+
552
+ === Auto Generate Queries and Mutations
553
+
554
+ To reduce the amount of boilerplate code a user has to write we auto-generate top-level CRUD queries and mutations for all types.
555
+
556
+ This is configurable via the API, you can:
557
+
558
+ * disable auto-generation (for mutations/queries)
559
+ * disable it per type
560
+ * disable mutations per operation (create,delete,update)
561
+
562
+ For a schema like this:
563
+
564
+ [source,graphql]
565
+ ----
566
+ type Person {
567
+ id:ID!
568
+ name: String
569
+ age: Int
570
+ }
571
+ ----
572
+
573
+
574
+ It would auto-generate quite a lot of things:
575
+
576
+ * a query: `person(id:ID, name:String , age: Int, _id: Int, filter:_PersonFilter, orderBy:_PersonOrdering, first:Int, offset:Int) : [Person]`
577
+ * a `_PersonOrdering` enum, for the `orderBy` argument with all fields for `_asc` and `_desc` sort order
578
+ * a `_PersonInput` for creating Person objects
579
+ * a `_PersonFilter` for the `filter` argument, which is a deeply nested input object (see <<filters>>)
580
+ * mutations for:
581
+ ** createPerson: `createPerson(id:ID!, name:String, age: Int) : Person`
582
+ ** updatePerson: `updatePerson(id:ID!, name:String, age:Int) : Person`
583
+ ** deletePerson: `deletePerson(id:ID!) : Person`
584
+
585
+ ////
586
+ ** addPersonMovie
587
+ ** deletePersonMovie
588
+ ////
589
+
590
+ You can then use those in your GraphQL queries like this:
591
+
592
+ [source,graphql]
593
+ ----
594
+ query { person(age:42, orderBy:name_asc) {
595
+ id
596
+ name
597
+ age
598
+ }
599
+ ----
600
+
601
+ or
602
+
603
+
604
+ [source,graphql]
605
+ ----
606
+ mutation {
607
+ createPerson(id: "34920n9qw0", name:"Jane Doe", age:42) {
608
+ id
609
+ name
610
+ age
611
+ }
612
+ }
613
+ ----
0 commit comments