diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 5614a4a60a..4d97c2d846 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -44,4 +44,6 @@ **** xref:guides/migration-guide/type-definitions/index.adoc[] **** xref:guides/migration-guide/queries/index.adoc[] **** xref:guides/migration-guide/mutations/index.adoc[] +*** xref:guides/rel-migration/index.adoc[] +**** xref:guides/rel-migration/mutations/index.adoc[] ** xref:troubleshooting/index.adoc[] diff --git a/docs/asciidoc/auth/setup.adoc b/docs/asciidoc/auth/setup.adoc index 4c3a59865d..5733939401 100644 --- a/docs/asciidoc/auth/setup.adoc +++ b/docs/asciidoc/auth/setup.adoc @@ -22,14 +22,17 @@ Auth centric values on the Config object passed to Neo4jGraphQL or OGM. | Specify JWT secret |`noVerify` -| Disable the verification of the JW +| Disable the verification of the JWT |`rolesPath` | Specify where on the JWT the roles key is |=== == Server Construction -Request object needs to be injected into the context before you can use auth. Here is an example using Apollo Server; + +*An object key `req` or `request` must be passed into the context before you can use auth.* This object must contain request headers, including the authorization header containing the JWT for each request. + +Here is an example using Apollo Server: [source, javascript] ---- @@ -48,6 +51,16 @@ const server = new ApolloServer({ }); ---- +We expect there to be a `headers` key within either a `req` or `request` object in the context. If for example, you are using `apollo-server-lambda` to host a GraphQL API as a lambda function, the context function will have an `event` object which will need to be renamed to `req`. For example: + +[source, javascript] +---- +const server = new ApolloServer({ + schema: neoSchema.schema, + context: ({ event }) => ({ req: event }), +}); +---- + == `rules` You can have many rules for many operations. We fall through each rule, on the corresponding operation, until we find a match. On no match found, an error is thrown. You can think of rules as a big OR. @@ -83,8 +96,12 @@ mutation { createPosts( input: [ { - content: "I like GraphQL", - creator: { connect: { where: { id: "user-01" } } } + content: "I like GraphQL" + creator: { + connect: { + where: { id: "user-01" } + } + } } ] ) { diff --git a/docs/asciidoc/custom-resolvers.adoc b/docs/asciidoc/custom-resolvers.adoc index d53a3f9291..6c48357a1e 100644 --- a/docs/asciidoc/custom-resolvers.adoc +++ b/docs/asciidoc/custom-resolvers.adoc @@ -3,6 +3,10 @@ The library will autogenerate Query and Mutation resolvers, so you don’t need to implement those resolvers yourself. However, if you would like additional behaviours besides the autogenerated CRUD operations, you can specify custom resolvers for these scenarios. +*A note on custom resolvers* + +> Due to the nature of the Cypher generation in this library, you must query any fields used in a custom resolver. For example, in the first example below calculating `fullName`, `firstName` and `lastName` must be included in the selection set whilst querying `fullName`. Without this being the case, `firstName` and `lastName` will be undefined in the custom resolver. + == Custom object type field resolver If you would like to add a field to an object type which is resolved from existing values in the type, rather than storing new values, you should mark it with an <> directive and define a custom resolver for it. diff --git a/docs/asciidoc/guides/2.0.0-migration/index.adoc b/docs/asciidoc/guides/2.0.0-migration/index.adoc new file mode 100644 index 0000000000..8d04e29df6 --- /dev/null +++ b/docs/asciidoc/guides/2.0.0-migration/index.adoc @@ -0,0 +1,18 @@ + +[[rel-migration]] += 2.0.0 Migration + +Version 2.0.0 of `@neo4j/graphql` adds support for relationship properties, with some breaking changes to facilitate these new features. All of the required changes will be on the client side, and this guide will walk through what has changed. + +== How to Upgrade + +Simply update `@neo4j/graphql` using npm or your package manager of choice: + +[source, bash] +---- +npm update @neo4j/graphql +---- + +From this point on, it is primarily Mutations which will form the bulk of the migration: + +1. <> for how you need to change your Mutations to work with the new schema diff --git a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc new file mode 100644 index 0000000000..3f679db1c6 --- /dev/null +++ b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc @@ -0,0 +1,359 @@ +[[rel-migration-mutations]] += Mutations + +The most broadly affected area of functionality by the 2.0.0 upgrade are the nested operations of Mutations, to faciliate the mutation of and filtering on relationship properties. + +The examples in this section will be based off the following type definitions (which have been migrated over to `@neo4j/graphql` syntax): + +[source, graphql] +---- +type Actor { + name: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +type Movie { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} +---- + +The theme that you will notice during this section is that as a general rule of thumb, a `node` field will need adding to your inputs where it will also be possible to filter on relationship properties. + +[[rel-migration-mutations-create]] +== Create + +Focussing on the `createMovies` Mutation, notice that the definition of the `createMovies` Mutation is unchanged: + +[source, graphql] +---- +input MovieCreateInput { + title: String! + actors: MovieActorsFieldInput +} + +type Mutation { + createMovies(input: [MovieCreateInput!]!): CreateMoviesMutationResponse! +} +---- + +There are no changes to any of the arguments or types at this level. However, within its nested operations, type modifications have taken place to allow for relationship properties. + +In practice, let's look at a Mutation that creates the film "The Dark Knight" and then: + +* Creates a new actor "Heath Ledger" +* Connects to the existing actor "Christian Bale" + +In the previous version of the library, this would have looked like this: + +[source, graphql] +---- +mutation { + createMovies( + input: [ + { + title: "The Dark Knight" + actors: { + create: [ + { + name: "Heath Ledger" + } + ] + connect: [ + { + where: { + name: "Christian Bale" + } + } + ] + } + } + ] + ) { + movies { + title + } + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql] +---- +mutation { + createMovies( + input: [ + { + title: "The Dark Knight" + actors: { + create: [ + { + node: { + name: "Heath Ledger" + } + } + ] + connect: [ + { + where: { + name: "Christian Bale" + } + } + ] + } + } + ] + ) { + movies { + title + } + } +} +---- + +Note the additional level "node" before specifying the actor name for the create operation. This additional level allows for the setting of relationship properties for the new relationship, and filtering on existing relationship properties when looking for the node to connect to. See the page <> for details on this. + +== Update + +Focussing on the `updateMovies` Mutation, notice that the definition of the `updateMovies` Mutation is unchanged: + +[source, graphql] +---- +type Mutation { + updateMovies( + where: MovieWhere + update: MovieUpdateInput + connect: MovieConnectInput + disconnect: MovieDisconnectInput + create: MovieRelationInput + delete: MovieDeleteInput + ): UpdateMoviesMutationResponse! +} +---- + +The `create` and `connect` nested operations are primarily the same as in the `createMovies` Mutation, so please see the <> section for the difference for these operations. + +The `delete` nested operation is primarily the same as in the `deleteMovies` Mutation, so please see the <> section for that. + +=== Update + +Let's say that we accidentally misspelt Christian Bale's surname and wanted to fix that. In the previous version, we might have achieved that by: + +[source, graphql] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + update: { + actors: [ + { + where: { + name_ENDS_WITH: "Bail" + } + update: { + name: "Christian Bale" + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + update: { + actors: [ + { + where: { + node: { + name_ENDS_WITH: "Bail" + } + } + update: { + name: "Christian Bale" + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +Note that for update operations, only the filter for the nested operation needs an additional level `node` adding, whilst the update itself remains the same. + +=== Disconnect + +Let's say we mistakenly put Ben Affleck as playing the role of Batman in "The Dark Knight" (I shall not share my opinion on the matter...), and we wanted to disconnect those nodes. In the previous version, this would have looked like: + +[source, graphql] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + disconnect: { + actors: [ + { + where: { + name: "Ben Affleck" + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + disconnect: { + actors: [ + { + where: { + node: { + name: "Ben Affleck" + } + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +[[rel-migration-mutations-delete]] +== Delete + +Focussing on the `deleteMovies` Mutation, notice that the definition of the `deleteMovies` Mutation is unchanged: + +[source, graphql] +---- +input MovieDeleteInput { + actors: [MovieActorsDeleteFieldInput!] +} + +type Mutation { + deleteMovies(where: MovieWhere, delete: MovieDeleteInput): DeleteInfo! +} +---- + +There are no changes to any of the arguments or types at this level, but let's dive into the `MovieActorsDeleteFieldInput` type. + +Previously, you would have expected this to look like: + +[source, graphql] +---- +input MovieActorsDeleteFieldInput { + delete: ActorDeleteInput + where: ActorWhere +} +---- + +This allowed you to filter on fields of the `Actor` type and delete based on that. However, following this upgrade, you will find: + +[source, graphql] +---- +input MovieActorsDeleteFieldInput { + delete: ActorDeleteInput + where: MovieActorsConnectionWhere +} +---- + +This means that not only can you filter on node properties, but also relationship properties, in order to find and delete `Actor` nodes. + +In practice, a Mutation that deletes the film "The Dark Knight" and the related actor "Christian Bale" would have previously looked like this: + +[source, graphql] +---- +mutation { + deleteMovies( + where: { + title: "The Dark Knight" + } + delete: { + actors: { + where: { + name: "Christian Bale" + } + } + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql] +---- +mutation { + deleteMovies( + where: { + title: "The Dark Knight" + } + delete: { + actors: { + where: { + node: { + name: "Christian Bale" + } + } + } + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +Note the additional level "node" before specifying the actor name. diff --git a/docs/asciidoc/guides/index.adoc b/docs/asciidoc/guides/index.adoc index 54e6f9f573..575634582e 100644 --- a/docs/asciidoc/guides/index.adoc +++ b/docs/asciidoc/guides/index.adoc @@ -4,3 +4,4 @@ Here you can find a selection of guides to help you work with the Neo4j GraphQL Library. 1. <> - migrating from `neo4j-graphql-js` to `@neo4j/graphql` +2. <<2.0.0-migration>> - migrating from version 1.* of `@neo4j/graphql` to version 2.* for relationship property support diff --git a/docs/asciidoc/guides/migration-guide/mutations.adoc b/docs/asciidoc/guides/migration-guide/mutations.adoc index 307308ed55..9d10271861 100644 --- a/docs/asciidoc/guides/migration-guide/mutations.adoc +++ b/docs/asciidoc/guides/migration-guide/mutations.adoc @@ -191,7 +191,7 @@ Would become the following using `@neo4j/graphql`: mutation { updateMovies( where: { title: "Forrest Gump" } - disconnect: { actors: { where: { name: "Tom Hanks" } } } + disconnect: { actors: { where: { node: { name: "Tom Hanks" } } } } ) { movies { title diff --git a/docs/asciidoc/guides/migration-guide/type-definitions.adoc b/docs/asciidoc/guides/migration-guide/type-definitions.adoc index e74b552647..8c433c345a 100644 --- a/docs/asciidoc/guides/migration-guide/type-definitions.adoc +++ b/docs/asciidoc/guides/migration-guide/type-definitions.adoc @@ -3,8 +3,6 @@ This page will walk through what needs to change in your type definitions before you can pass them into `@neo4j/graphql`. -> An important discrepancy to note at this early stage is that relationship type definitions are not yet supported in `@neo4j/graphql`, and as such, neither are relationship properties. This is in the pipeline, so keep an eye out for any news relating to this! - == Directives Both `neo4j-graphql-js` and `@neo4j/graphql` are highly driven by GraphQL directives. Each heading in this section will address how/if one or many directives available in `neo4j-graphql-js` can be migrated to `@neo4j/graphql`. @@ -20,6 +18,55 @@ For example, `@relation(name: "ACTED_IN", direction: OUT)` becomes `@relationshi See <> for more information on relationships in `@neo4j/graphql`. +=== Relationship Properties + +If for instance using `neo4j-graphql-js`, you have the following type definitions defining an `ACTED_IN` relationship with a `roles` property: + +[source, graphql] +---- +type Actor { + movies: [ActedIn!]! +} + +type Movie { + actors: [ActedIn!]! +} + +type ActedIn @relation(name: "ACTED_IN") { + from: Actor + to: Movie + roles: [String!] +} +---- + +This will need to be refactored to the following in the new library: + +[source, graphql] +---- +type Actor { + movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) +} + +type Movie { + actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) +} + +interface ActedIn { + roles: [String!] +} +---- + +Note the following changes to the `ActedIn` type: + +* Changed from `type` to `interface` +* Removed `@relation` directive +* Removed `from` and `to` fields + +And note the following changes to the two node types: + +* Relationship field types changed from the relationship type to the neighbouring node type +* Normal `@relationship` directive added to each relationship field, with an additional `properties` argument pointing to the relationship properties interface + === `@cypher` No change. See <> for more details on this directive in `@neo4j/graphql`. @@ -104,3 +151,16 @@ Interface Types are not yet supported in `@neo4j/graphql`. `neo4j-graphql-js` le === Union Types Supported, queryable using inline fragments as per `neo4j-graphql-js`, but can also be created using Nested Mutations. See <>. + +== Fields + +=== `_id` + +An `_id` field exposing the underlying node ID is not included in each type by default in `@neo4j/graphql` like it was in `neo4j-graphql-js`. If you require this functionality (however, it should be noted that underlying node IDs should not be relied on because they can be reused), you can include a field definition such as in the following type definition: + +[source, graphql] +---- +type ExampleType { + _id: ID! @cypher(statement: "RETURN ID(this)") +} +---- diff --git a/docs/asciidoc/schema/mutations.adoc b/docs/asciidoc/schema/mutations.adoc index 77565150e6..8d00c3b6b6 100644 --- a/docs/asciidoc/schema/mutations.adoc +++ b/docs/asciidoc/schema/mutations.adoc @@ -90,7 +90,9 @@ mutation { posts: { create: [ { - content: "Hi, my name is John!" + node: { + content: "Hi, my name is John!" + } } ] } @@ -178,7 +180,7 @@ mutation { where: { name: "John Doe" } create: { posts: [ - { content: "An interesting way of adding a new Post!" } + { node: { content: "An interesting way of adding a new Post!" } } ] } ) { @@ -274,8 +276,10 @@ mutation { delete: { posts: [ where: { - creator: { - name: "Jane Doe" + node: { + creator: { + name: "Jane Doe" + } } } ] diff --git a/docs/asciidoc/type-definitions/relationships.adoc b/docs/asciidoc/type-definitions/relationships.adoc index d810b3e1a0..40384d50ff 100644 --- a/docs/asciidoc/type-definitions/relationships.adoc +++ b/docs/asciidoc/type-definitions/relationships.adoc @@ -7,11 +7,11 @@ Without relationships, your type definitions are simply a collection of disconne We will be using the following example graph, where a Person type has two different relationship types which can connect it to a Movie type. -image::relationships.png[title="Example graph"] +image::relationships.svg[title="Example graph"] == Type definitions -First, we should define the two distinct types in this model: +First, to define the nodes, we should define the two distinct types in this model: [source, graphql] ---- @@ -51,6 +51,36 @@ The following should be noted about the fields we just added: * A Movie isn't really a Movie without a director, and we have signified this by marking the `director` field as non-nullable, meaning that a Movie must have a `DIRECTED` relationship coming into it. * To figure out whether the `direction` argument of the `@relationship` directive should be `IN` or `OUT`, visualise your relationships like in the diagram above, and model out the direction of the arrows. +=== Relationship Properties + +Relationship properties can be added to the above type definitions in two steps: + +1. Add an interface definition containing the desired relationship properties +2. Add a `properties` argument to both "sides" of the `@relationship` directive which points to the newly defined interface + +For example, to distinguish which roles an actor played in a movie: + +[source, graphql] +---- +type Person { + name: String! + born: Int! + actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) + directedMovies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT) +} + +type Movie { + title: String! + released: Int! + actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) + director: Person! @relationship(type: "DIRECTED", direction: IN) +} + +type ActedIn { + roles: [String!] +} +---- + == Inserting data Nested mutations mean that there are many ways in which we can insert data into our database through the GraphQL schema. We know that we can't create a Movie without adding a director, and we can do that by either creating the director first and then creating and connecting the movie, or we can create both the Movie and the director in the same mutation. Let's try the latter: @@ -64,8 +94,10 @@ mutation CreateMovieAndDirector { released: 1994 director: { create: { - name: "Robert Zemeckis" - born: 1951 + node: { + name: "Robert Zemeckis" + born: 1951s + } } } } @@ -82,7 +114,7 @@ mutation CreateMovieAndDirector { } ---- -We then need to create the actor in our example, and connect them to the new Movie node: +We then need to create the actor in our example, and connect them to the new Movie node, also specifying which roles they played: [source, graphql] ---- @@ -96,6 +128,9 @@ mutation CreateActor { where: { title: "Forrest Gump" } + properties: { + roles: ["Forrest"] + } } } } @@ -107,15 +142,22 @@ mutation CreateActor { name born } - actors { - name - born + actorsConnection { + edges { + roles + node { + name + born + } + } } } } } ---- +Note that we select the `actorsConnection` field in order to query the `roles` relationship property. + As you can see, these nested mutations are very powerful, and in the second Mutation we ran, we were able to return the entire graph which was created in this example. In fact, these mutations can actually be compressed down into a single Mutation which inserts all of the data we need: [source, graphql] @@ -127,15 +169,22 @@ mutation CreateMovieDirectorAndActor { released: 1994 director: { create: { - name: "Robert Zemeckis" - born: 1951 + node: { + name: "Robert Zemeckis" + born: 1951 + } } } actors: { create: [ { - name: "Tom Hanks" - born: 1956 + node: { + name: "Tom Hanks" + born: 1956 + } + properties: { + roles: ["Forrest"] + } } ] } @@ -148,9 +197,14 @@ mutation CreateMovieDirectorAndActor { name born } - actors { - name - born + actorsConnection { + edges { + roles + node { + name + born + } + } } } } @@ -173,9 +227,14 @@ query { name born } - actors { - name - born + actorsConnection { + edges { + roles + node { + name + born + } + } } } } diff --git a/docs/asciidoc/type-definitions/types.adoc b/docs/asciidoc/type-definitions/types.adoc index df3f4a5b6e..dc1fee671f 100644 --- a/docs/asciidoc/type-definitions/types.adoc +++ b/docs/asciidoc/type-definitions/types.adoc @@ -46,6 +46,19 @@ Neo4j GraphQL spatial types translate to spatial values stored using https://neo The `Point` type is used to describe the two https://neo4j.com/docs/cypher-manual/current/syntax/spatial/#cypher-spatial-crs-geographic[Geographic coordinate reference systems] supported by Neo4j. +In order to use it in your schema, you quite simply add a field with a type `Point` to any type or types in schema, like the following: + +[source, graphql] +---- +type TypeWithPoint { + location: Point! +} +---- + +Once this has been done, the `Point` type will be automatically added to your schema, in addition to all of the input and output types you will need to query and manipulate spatial types through your API. + +The rest of the documentation under this heading is documenting all of those automatically generated types and how to use them. + ==== Type definition [source, graphql] @@ -146,6 +159,19 @@ query CloseByUsers($longitude: Float!, $latitude: Float!) { The `CartesianPoint` type is used to describe the two https://neo4j.com/docs/cypher-manual/current/syntax/spatial/#cypher-spatial-crs-cartesian[Cartesian coordinate reference systems] supported by Neo4j. +In order to use it in your schema, you quite simply add a field with a type `CartesianPoint` to any type or types in schema, like the following: + +[source, graphql] +---- +type TypeWithCartesianPoint { + location: CartesianPoint! +} +---- + +Once this has been done, the `CartesianPoint` type will be automatically added to your schema, in addition to all of the input and output types you will need to query and manipulate spatial types through your API. + +The rest of the documentation under this heading is documenting all of those automatically generated types and how to use them. + ==== Type definition [source, graphql] diff --git a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc index 8937c60d17..0bdbc86fd8 100644 --- a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc +++ b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc @@ -59,19 +59,28 @@ The below mutation creates the user and their content; [source, graphql] ---- mutation CreateUserAndContent { - createUsers( - input: [ - { - name: "Dan" - content_Blog: { - create: [ - { - title: "My Cool Blog" - posts: { create: [{ content: "My Cool Post" }] } + createUsers(input: [ + { + name: "Dan" + content_Blog: { + create: [ + { + node: { + title: "My Cool Blog" + posts: { + create: [ + { + node: { + content: "My Cool Post" + } + } + ] + } + } + } + ] } - ] } - } ] ) { users { diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 09c0d4287e..70280ccf55 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -151,6 +151,12 @@ + + + + + + diff --git a/docs/images/relationships.png b/docs/images/relationships.png deleted file mode 100644 index 27aa3c9ef3..0000000000 Binary files a/docs/images/relationships.png and /dev/null differ diff --git a/docs/images/relationships.svg b/docs/images/relationships.svg new file mode 100644 index 0000000000..0de9bcf93d --- /dev/null +++ b/docs/images/relationships.svg @@ -0,0 +1,55 @@ + + + + + + +L + + + +N2 + +Person + +name = 'Tom Hanks' +born = 1956 + + + +N3 + +Movie + +title = 'Forrest Gump' +released = 1994 + + + +N2->N3 + + +ACTED_IN +roles = ['Forrest'] + + + +N4 + +Person + +name = 'Robert Zemeckis' +born = 1951 + + + +N4->N3 + + +DIRECTED + + +