Skip to content

Commit 9827175

Browse files
committed
Updated readme, fixed examples
1 parent 01e99e3 commit 9827175

File tree

4 files changed

+81
-54
lines changed

4 files changed

+81
-54
lines changed

docs/Server.groovy

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ def query(value) { gson.fromJson(value,Map.class)["query"] }
3535
graphql = new Translator(SchemaBuilder.buildSchema(schema))
3636
def translate(query) { graphql.translate(query) }
3737

38-
driver = GraphDatabase.driver("bolt://localhost",AuthTokens.basic("neo4j","password"))
39-
def run(cypher) { driver.session().withCloseable { it.run(cypher.query, Values.value(cypher.params)).list{ it.asMap() }}}
38+
driver = GraphDatabase.driver("bolt://localhost",AuthTokens.basic("neo4j","password"),
39+
Config.builder().withoutEncryption().build())
4040

41-
post("/graphql","application/json", { req, res -> run(translate(query(req.body())).first()) }, render);
41+
def run(cypher) { driver.session().withCloseable {
42+
it.run(cypher.query, Values.value(cypher.params)).list{ it.asMap() }}}
43+
44+
post("/graphql","application/json",
45+
{ req, res -> run(translate(query(req.body())).first()) }, render);

readme.adoc

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
= JVM Library to translate GraphQL queries and mutations to Neo4j's Cypher
22
:version: 1.0.0
3+
:toc:
4+
:toclevels: 1
5+
:toc-title: Quick Links
36

4-
This is a GraphQL transpiler written in Kotlin.
7+
This is a https://graphql.org[GraphQL] to https://neo4j.com/developer/cypher[Cypher] transpiler written in Kotlin.
58

69
License: Apache 2.
710

@@ -16,17 +19,19 @@ Those Cypher queries can then executed, e.g via the Neo4j-Java-Driver (or other
1619

1720
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.
1821

19-
NOTE: All the <<features|supported features>> are listed and explained below, more detailed docs will be added in time.
22+
NOTE: All the <<features,supported features>> are listed and explained below, more detailed docs will be added in time.
2023

2124
== FAQ
2225

2326
=== How does this relate to the other neo4j graphql libraries?
2427

25-
Similar to `neo4j-graphql-js` this library focuses on query translation, just for the *JVM* instead of Node.js.
28+
https://grandstack.io[The GRANDstack^] is a full-stack package that integrates React frontends via GraphQL through `neo4j-graphql-js` with Neo4j.
29+
30+
Similar to https://grandstack.io/docs/neo4j-graphql-js-quickstart[`neo4j-graphql-js`] this library focuses on query translation, just for the *JVM* instead of Node.js.
2631
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.
2732

28-
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.
29-
The server plugin would still handle request-response and error-handling, and perhaps some schema management but be slimmed down to a tiny piece.
33+
We plan to replace the code in the current Neo4j server plugin `neo4j-graphql` with a single call to this library.
34+
The server plugin could still exist as an example that shows how to handle request-response and error-handling, and perhaps some minimal schema management but be slimmed down to a tiny piece of code.
3035

3136
=== How does this related to graphql-java
3237

@@ -37,6 +42,8 @@ If you wanted, you could combine `graphql-java` resolvers with this library to h
3742

3843
Thanks a lot to the maintainers of `graphql-java` for the awesome library.
3944

45+
NOTE: We also use `neo4j-opencypher-dsl` provided graciously by the spring-data-neo4j-rx project to generate parts of the cypher queries.
46+
4047
== Usage
4148

4249
You can use the library as dependency: `org.neo4j:neo4j-graphql-java:{version}` in any JVM program.
@@ -61,8 +68,6 @@ val query = """ { p:personByName(name:"Joe") { age } } """
6168
6269
val schema = SchemaBuilder.buildSchema(idl)
6370
val ctx = QueryContext()
64-
// SETUP otimizer strategy
65-
// ctx.optimizedQuery = setOf(QueryContext.OptimizationStrategy.FILTER_AS_MATCH)
6671
val (cypher, params) = Translator(schema).translate(query, params, ctx)
6772
6873
// generated Cypher
@@ -83,7 +88,7 @@ You find more usage examples in the:
8388
== Demo
8489

8590
Here is a minimalistic example in Groovy using the Neo4j-Java driver and Spark-Java as webserver.
86-
It is running against a Neo4j instance at `bolt://localhost` (username: `neo4j`, password: `password`) containing the `:play movies` graph.
91+
It is running against a Neo4j instance at `bolt://localhost` (username: `neo4j`, password: `s3cr3t`) containing the `:play movies` graph.
8792

8893
(You can also use a link:src/test/kotlin/GraphQLServer.kt[Kotlin based server example].)
8994

@@ -97,8 +102,8 @@ intercept the cypher queries].
97102
@Grapes([
98103
@Grab('com.sparkjava:spark-core:2.7.2'),
99104
@Grab('org.neo4j.driver:neo4j-java-driver:1.7.2'),
100-
@Grab('org.neo4j:neo4j-graphql-java:{version}'),
101-
@Grab('com.google.code.gson:gson:2.8.5')
105+
@Grab('com.google.code.gson:gson:2.8.5'),
106+
@Grab('org.neo4j:neo4j-graphql-java:{version}')
102107
])
103108
104109
import spark.*
@@ -130,8 +135,9 @@ def query(value) { gson.fromJson(value,Map.class)["query"] }
130135
graphql = new Translator(SchemaBuilder.buildSchema(schema))
131136
def translate(query) { graphql.translate(query) }
132137
133-
driver = GraphDatabase.driver("bolt://localhost",AuthTokens.basic("neo4j","password"))
134-
def run(cypher) { driver.session().withCloseable { it.run(cypher.query, Values.value(cypher.params)).list{ it.asMap() }}}
138+
driver = GraphDatabase.driver("bolt://localhost",AuthTokens.basic("neo4j","s3cr3t"))
139+
def run(cypher) { driver.session().withCloseable {
140+
it.run(cypher.query, Values.value(cypher.params)).list{ it.asMap() }}}
135141
136142
post("/graphql","application/json", { req, res -> run(translate(query(req.body())).first()) }, render);
137143
----
@@ -187,7 +193,7 @@ You can also test it with `curl`
187193
curl -XPOST http://localhost:4567/graphql -d'{"query":"{person {name}}"}'
188194
----
189195

190-
This example doesn't handle introspection queries but the one in the test directory does.
196+
This example doesn't handle introspection queries, but the one in the test directory does.
191197

192198
== Advanced Queries
193199

@@ -239,6 +245,7 @@ This example doesn't handle introspection queries but the one in the test direct
239245
* @cypher directive for top level queries and mutations, supports arguments
240246
* date(time)
241247
* interfaces
248+
* complex filter parameters, with optional query optimization strategy
242249

243250
=== Next
244251

@@ -253,16 +260,16 @@ This example doesn't handle introspection queries but the one in the test direct
253260

254261
=== Parse SDL schema
255262

256-
Currently schemas with object types, enums, fragments and Query types are parsed and handled.
257-
We support @relation directives on fields and types for rich relationships
258-
We support @cypher directives on fields and top-level query and mutation fields.
259-
The configurable augmentation auto-generates queries and mutations (create,update,delete) for all types.
260-
It supports the built-in scalars for GraphQL.
261-
For arguments we support input types in many places and filters as known from GraphCool/Prisma.
263+
* Currently schemas with object types, enums, fragments and Query types are parsed and handled.
264+
* `@relation` directives on fields and types for rich relationships
265+
* `@cypher` directives on fields and top-level query and mutation fields.
266+
* The configurable augmentation auto-generates queries and mutations (create,update,delete) for all types.
267+
* Supports the built-in scalars for GraphQL.
268+
* For arguments input types in many places and filters from GraphCool/Prisma.
262269

263270
=== Resolve query Fields via Result Types
264271

265-
For _query fields_ that result in object types (even if wrapped in list/non-null), the appropriate object type is found in the schema and used to translate the query.
272+
For _query fields_ that result in object types (even if wrapped in list/non-null), the appropriate object type is determined via the schema and used to translate the query.
266273

267274
e.g.
268275

@@ -289,19 +296,19 @@ person(name:"Joe", age:42) {
289296
}
290297
----
291298

292-
to
299+
to an equivalent of
293300

294301
[source,cypher]
295302
----
296303
MATCH (person:Person) WHERE person.name = 'Joe' AND person.age = 42 RETURN person { .name } AS person
297304
----
298305

299-
Only that the literal values are turned into parameters.
306+
The literal values are turned into Cypher query parameters.
300307

301308
=== Handle Relationships via @relation Directive on Schema Fields
302309

303-
If you want to represent a relationship from the graph in GraphQL you have to add an `@relation` directive that contains the relationship-type and the direction.
304-
Default relationship-type is 'OUT'.
310+
If you want to represent a relationship from the graph in GraphQL you have to add a `@relation` directive which contains the relationship-type and the direction.
311+
The default relationship-type is 'OUT'.
305312
So you can use different domain names in your GraphQL fields that are independent of your graph model.
306313

307314
[source,graphql]
@@ -323,6 +330,7 @@ person(name:"Keanu Reeves") {
323330
----
324331

325332
NOTE: We use Neo4j's _pattern comprehensions_ to represent nested graph patterns in Cypher.
333+
This will be updated to subqueries from 4.1
326334

327335
=== Handle first, offset Arguments
328336

@@ -343,13 +351,13 @@ MATCH (person:Person) RETURN person { .name } AS person SKIP 5 LIMIT 10
343351

344352
=== Argument Types: string, int, float, array
345353

346-
The default Neo4j types are handled both as argument types as well as field types.
354+
The default Neo4j Cypher types are handled both as argument types as well as field types.
347355

348-
NOTE: Datetime and spatial not yet.
356+
NOTE: Spatial is not yet covered.
349357

350358
=== Usage of ID
351359

352-
Each type is expect to have exactly one filed of type `ID` defined.
360+
Each type is expected to have exactly one filed of type `ID` defined.
353361
If the field is named `_id`, it is interpreted as the database internal graph ID.
354362

355363
So there are 3 cases:
@@ -383,17 +391,17 @@ type User {
383391
}
384392
----
385393

386-
IMPORTANT: For the auto generated queries and mutations the `ID` field is used primarily.
394+
IMPORTANT: For the auto generated queries and mutations the `ID` field is used as _primary key_.
387395

388-
TIP: You should create an unique index on the `ID` fields
396+
TIP: You should create a unique constraint on the `ID` fields
389397

390398
=== Parameter Support
391399

392-
We handle passed in GraphQL parameters, these are resolved correctly when used within the GraphQL query.
400+
GraphQL parameters are passed onto Cypher, these are resolved correctly when used within the GraphQL query.
393401

394402
=== Parametrization
395403

396-
As we don't want to have literal values in our Cypher queries, all of them are translated into parameters.
404+
For query injection prevention and caching purposes, literal values are translated into parameters.
397405

398406
[source,graphql]
399407
----
@@ -406,7 +414,10 @@ to
406414

407415
[source,cypher]
408416
----
409-
MATCH (person:Person) WHERE person.name = $personName AND person.age = $personAge RETURN person { .name } AS person LIMIT $first
417+
MATCH (person:Person)
418+
WHERE person.name = $personName AND person.age = $personAge
419+
RETURN person { .name } AS person
420+
LIMIT $first
410421
----
411422

412423
Those parameters are returned as part of the `Cypher` type that's returned from the `translate()` method.
@@ -452,7 +463,7 @@ ORDER BY person.name ASC, person.age DESC
452463
----
453464

454465

455-
NOTE: Those enums are not yet automatically generated. And we don't support ordering yet on nested, related fields.
466+
NOTE: We don't yet support ordering on nested relationship fields.
456467

457468
=== @relationship on Types
458469

@@ -495,12 +506,11 @@ person(name:"Keanu Reeves") {
495506
=== Filters
496507

497508
Filters are a powerful way of selecting a subset of data.
498-
Inspired by the https://www.graph.cool/docs/reference/graphql-api/query-api-nia9nushae[graph.cool/Prisma filter approach], our filters work the same way.
499-
500-
NOTE: we'll create more detailed docs, for now the prisma docs on that topic are pretty good.
509+
Inspired by the https://www.graph.cool/docs/reference/graphql-api/query-api-nia9nushae[graph.cool/Prisma filter approach^], our filters work the same way.
501510

511+
These filters are documented in detail in the https://grandstack.io/docs/graphql-filtering [GRANDstack docs^].
502512

503-
We use nested input types for arbitrary filtering on query types and fields
513+
We use nested input types for arbitrary filtering on query types and fields.
504514

505515
[source,graphql]
506516
----
@@ -521,8 +531,6 @@ You can also apply nested filter on relations, which use suffixes like `("",not,
521531
}
522532
----
523533

524-
NOTE: Those nested input types are not yet generated, we use leniency in the parser.
525-
526534
==== Optimized Filters
527535

528536
If you encounter performance problems with the cypher queries generated for the filter,
@@ -574,8 +582,10 @@ query {
574582
=== @cypher Directives
575583

576584
With `@cypher` directives you can add the power of Cypher to your GraphQL API.
577-
It allows you, without code to compute field values using complex queries.
578-
You can also write your own, custom top-level queries and mutations using Cypher.
585+
586+
It allows you, without code to *compute field values* using complex queries.
587+
588+
You can also write your own, *custom top-level queries and mutations* using Cypher.
579589

580590
Arguments on the field are passed to the Cypher statement as parameters.
581591
Input types are supported, they appear as `Map` type in your Cypher statement.
@@ -590,19 +600,22 @@ NOTE: Those Cypher directive queries are only included in the generated Cypher s
590600
type Movie {
591601
title: String
592602
released: Int
593-
similar(limit:Int=10): [Movie] @cypher(statement:"
603+
similar(limit:Int=10): [Movie] @cypher(statement:
604+
"""
594605
MATCH (this)-->(:Genre)<--(sim)
595606
WITH sim, count(*) as c ORDER BY c DESC LIMIT $limit
596-
RETURN sim")
607+
RETURN sim
608+
""")
597609
}
598610
----
599611

600-
Here the `this` variable is bound to the current movie and you can use it to navigate the graph and collect data.
612+
Here the `this` variable is bound to the current movie.
613+
You can use it to navigate the graph and collect data.
601614
The `limit` variable is passed to the query as parameter.
602615

603616
==== On Queries
604617

605-
Similarly you can use the `@cypher` directive with a top level query.
618+
Similarly, you can use the `@cypher` directive with a top-level query.
606619

607620
.@cypher directive on query
608621
[source,graphql]
@@ -612,7 +625,7 @@ type Query {
612625
}
613626
----
614627

615-
Of course you can also return arrays from your query, the statements on query fields should be read-only queries.
628+
You can also return arrays from your query, the statements on query fields should be read-only queries.
616629

617630
==== On Mutations
618631

@@ -630,14 +643,14 @@ You can use more complex statements for creating these entities or even subgraph
630643

631644
NOTE: The common CRUD mutations and queries are auto-generated, see below.
632645

633-
=== Auto Generate Queries and Mutations
646+
=== Auto Generated Queries and Mutations
634647

635-
To reduce the amount of boilerplate code a user has to write we auto-generate top-level CRUD queries and mutations for all types.
648+
To reduce the amount of boilerplate code you have to write we auto-generate top-level CRUD queries and mutations for all types.
636649

637650
This is configurable via the API, you can:
638651

639652
* disable auto-generation (for mutations/queries)
640-
* disable it per type
653+
* disable per type
641654
* disable mutations per operation (create,delete,update)
642655

643656
For a schema like this:

src/main/kotlin/org/neo4j/graphql/handler/relation/DeleteRelationHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class DeleteRelationHandler private constructor(
2121
return
2222
}
2323
type.fieldDefinitions
24-
.filter { canHandleField(it) }
24+
.filterg { canHandleField(it) }
2525
.mapNotNull { targetField ->
2626
buildFieldDefinition(type, targetField, true)
2727
?.let { builder -> buildingEnv.addOperation(MUTATION, builder.build()) }

src/test/kotlin/GraphQLServer.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package demo
22

33
// Simplistic GraphQL Server using SparkJava
4+
// curl -H content-type:application/json -d'{"query": "{ movie { title, released }}"}' http://localhost:4567/graphql
5+
// GraphiQL: https://neo4j-graphql.github.io/graphiql4all/index.html?graphqlEndpoint=http%3A%2F%2Flocalhost%3A4567%2Fgraphql&query=query%20%7B%0A%20%20movie%20%7B%20title%7D%0A%7D
46

57
import com.google.gson.Gson
68
import graphql.GraphQL
79
import org.neo4j.driver.v1.AuthTokens
10+
import org.neo4j.driver.v1.Config
811
import org.neo4j.driver.v1.GraphDatabase
912
import org.neo4j.driver.v1.Values
1013
import org.neo4j.graphql.*
@@ -53,7 +56,7 @@ fun main() {
5356
translator.translate(query, params)
5457
}
5558

56-
val driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"))
59+
val driver = GraphDatabase.driver("bolt://localhost", AuthTokens.basic("neo4j", "test"), Config.build().withoutEncryption().build())
5760
fun run(cypher: Cypher) = driver.session().use {
5861
println(cypher.query)
5962
println(cypher.params)
@@ -70,6 +73,12 @@ fun main() {
7073
}
7174
}
7275

76+
// CORS
77+
Spark.before("/*") { req, res ->
78+
res.header("Access-Control-Allow-Origin", "*");
79+
res.header("Access-Control-Allow-Headers", "*");
80+
res.type("application/json");
81+
}
7382

7483
fun handler(req: Request, @Suppress("UNUSED_PARAMETER") res: Response) = req.body().let { body ->
7584
val payload = parseBody(body)
@@ -79,6 +88,7 @@ fun main() {
7988
else run(translate(query, params(payload)).first())
8089
}
8190

91+
Spark.options("/graphql") { _, _ -> "OK" }
8292
Spark.post("/graphql", "application/json", ::handler, ::render)
8393
}
8494

0 commit comments

Comments
 (0)