From 96cb57803fc295add47160c4a37cc4b5e843f958 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Tue, 27 Jul 2021 09:53:03 +0100 Subject: [PATCH 01/20] Docs changes --- docs/antora/content-nav.adoc | 5 +- .../guides/2.0.0-migration/index.adoc | 6 +- .../guides/2.0.0-migration/miscellaneous.adoc | 48 ++++++++++ .../guides/2.0.0-migration/mutations.adoc | 10 +- .../guides/2.0.0-migration/unions.adoc | 94 ------------------- docs/asciidoc/guides/index.adoc | 2 +- docs/asciidoc/schema/pagination.adoc | 8 ++ docs/asciidoc/troubleshooting.adoc | 12 +++ docs/docbook/content-map.xml | 13 ++- 9 files changed, 89 insertions(+), 109 deletions(-) create mode 100644 docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc delete mode 100644 docs/asciidoc/guides/2.0.0-migration/unions.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 2bcd1eabc7..30e140f101 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -45,6 +45,7 @@ **** 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:guides/v2-migration/index.adoc[] +**** xref:guides/v2-migration/mutations/index.adoc[] +**** xref:guides/v2-migration/miscellaneous/index.adoc[] ** xref:troubleshooting/index.adoc[] diff --git a/docs/asciidoc/guides/2.0.0-migration/index.adoc b/docs/asciidoc/guides/2.0.0-migration/index.adoc index 8d04e29df6..362fac5816 100644 --- a/docs/asciidoc/guides/2.0.0-migration/index.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/index.adoc @@ -1,5 +1,5 @@ -[[rel-migration]] +[[v2-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. @@ -15,4 +15,6 @@ 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 +1. <> for how you need to change your Mutations to work with the new schema +2. <> for how querying union fields has changed in version 2.0.0 +3. <> for other changes in version 2.0.0 diff --git a/docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc b/docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc new file mode 100644 index 0000000000..ef26214fd8 --- /dev/null +++ b/docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc @@ -0,0 +1,48 @@ +[[v2-migration-miscellaneous]] += Miscellaneous + +== `skip` renamed to `offset` + +In the release of Apollo Client 3.0, it became a bit more opinionated about pagination, favouring `offset` and `limit` over `skip` and `limit`. Acknowledging that the majority of our user base will be using Apollo Client 3.0, we have updated the page-based pagination arguments to align with this change. + +For example, whilst fetching page 3 of pages of 10 movies would have looked like the following in version `1.x`: + +[source, graphql] +---- +query { + movies(options: { skip: 20, limit: 10 }) { + title + } +} +---- + +This will now need to queried as follows: + +[source, graphql] +---- +query { + movies(options: { offset: 20, limit: 10 }) { + title + } +} +---- + +== Count queries + +Whilst not a necessary migration step, if you are using page-based pagination, it's important to note the addition of count queries in version 2.0.0. These will allow you to calculate the total number of pages for a particular filter, allowing you to implement much more effective pagination. + +== Schema validation + +In version 2.0.0, we have added greater levels of schema validation. However, upon upgrading, you might find that validation is too strict (for example if using certain generated types in your definitions). You can temporarily disable this new validation on construction: + +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + skipValidateTypeDefs: true, + }, +}) +---- + +If you need to do this, please report the scenario as an issue on GitHub. diff --git a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc index c02316bd66..c4ec4eec10 100644 --- a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc @@ -1,4 +1,4 @@ -[[rel-migration-mutations]] +[[v2-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. @@ -20,7 +20,7 @@ type Movie { 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]] +[[v2-migration-mutations-create]] == Create Focussing on the `createMovies` Mutation, notice that the definition of the `createMovies` Mutation is unchanged: @@ -134,9 +134,9 @@ type Mutation { } ---- -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 `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. +The `delete` nested operation is primarily the same as in the `deleteMovies` Mutation, so please see the <> section for that. === Update @@ -272,7 +272,7 @@ mutation { } ---- -[[rel-migration-mutations-delete]] +[[v2-migration-mutations-delete]] == Delete Focussing on the `deleteMovies` Mutation, notice that the definition of the `deleteMovies` Mutation is unchanged: diff --git a/docs/asciidoc/guides/2.0.0-migration/unions.adoc b/docs/asciidoc/guides/2.0.0-migration/unions.adoc deleted file mode 100644 index 54047b859f..0000000000 --- a/docs/asciidoc/guides/2.0.0-migration/unions.adoc +++ /dev/null @@ -1,94 +0,0 @@ -[[rel-migration-unions]] -= Unions - -The structure of input types for union queries and mutations have been changed for user friendliness, and a more consistent API. - -The examples in this section will be based off the following type definitions: - -[source, graphql] ----- -type Actor { - name: String! - actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Series { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -union Production = Movie | Series ----- - -Essentially, field names which were previously of template `${unionFieldName}_${concreteType}` (for example, "actedIn_Movie") are now an object, with the field name at the top, and the member types under it. - -For example, a Mutation which would have previously been: - -[source, graphql] ----- -mutation { - createActors( - input: [ - { - name: "Tom Hiddleston" - actedIn_Movie: { - create: [ - { - title: "The Avengers" - } - ] - } - actedIn_Series: { - create: [ - { - title: "Loki" - } - ] - } - } - ] - ) -} ----- - -Will now be: - -[source, graphql] ----- -mutation { - createActors( - input: [ - { - name: "Tom Hiddleston" - actedIn: { - Movie: { - create: [ - { - node: { - title: "The Avengers" - } - } - ] - } - Series: { - create: [ - { - node: { - title: "Loki" - } - } - ] - } - } - } - ] - ) -} ----- - -Note the change in structure for union input, but also the additional `node` level which enables the use of relationship properties. These changes are consistent across all operations, including `where`. diff --git a/docs/asciidoc/guides/index.adoc b/docs/asciidoc/guides/index.adoc index 3d33558aaf..a052c2d189 100644 --- a/docs/asciidoc/guides/index.adoc +++ b/docs/asciidoc/guides/index.adoc @@ -4,4 +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. <> - migrating from version 1.* of `@neo4j/graphql` to version 2.* for relationship property support +2. <> - migrating from version 1.* of `@neo4j/graphql` to version 2.* for relationship property support diff --git a/docs/asciidoc/schema/pagination.adoc b/docs/asciidoc/schema/pagination.adoc index f3954b1d62..7f39850d64 100644 --- a/docs/asciidoc/schema/pagination.adoc +++ b/docs/asciidoc/schema/pagination.adoc @@ -55,6 +55,10 @@ query { } ---- +=== Total number of pages + +You can fetch the total number of records for a certain type using its count query, and then divide that number by your entries per page in order to calculate the total number of pages. This will allow to to determine what the last page is, and whether there is a next page. + === Paginating relationship fields Say that in addition to the `User` type above, there is also a `Post` type which a `User` has many of. You can also fetch a `User` and then paginate through their posts: @@ -75,3 +79,7 @@ query { } } ---- + +== Cursor-based pagination + +On relationship fields, you are able to take advantage of cursor-based pagination. diff --git a/docs/asciidoc/troubleshooting.adoc b/docs/asciidoc/troubleshooting.adoc index 1abe182c9c..27c7f0090e 100644 --- a/docs/asciidoc/troubleshooting.adoc +++ b/docs/asciidoc/troubleshooting.adoc @@ -63,3 +63,15 @@ server.listen().then(({ url }) => { console.log(`Server ready at ${url}`); }); ---- + +== I've upgraded from <1.1.0 and my `DateTime` fields aren't sorting as expected! + +Due to a bug in versions less than 1.1.0, there is a chance that your `DateTime` fields are stored in the database as strings instead of temporal values. You should perform a rewrite of those properties in your database using a Cypher query. For an example where the affected node has label "Movie" and the affected property is "timestamp", you can do this using the following Cypher: + +[source, javascript] +---- +MATCH (m:Movie) +WHERE apoc.meta.type(m.timestamp) = "STRING" +SET m.timestamp = datetime(m.timestamp) +RETURN m +---- diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 11c0a4eb6c..0242a362fc 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -154,10 +154,13 @@ - - - - + + + + + + + @@ -166,4 +169,4 @@ - \ No newline at end of file + From f61d9d6a3551fdb158d6b9f81137aa947b61bb73 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 14:35:09 +0100 Subject: [PATCH 02/20] Add unions page back to migration guide --- docs/antora/content-nav.adoc | 1 + .../guides/2.0.0-migration/unions.adoc | 98 +++++++++++++++++++ docs/docbook/content-map.xml | 3 + 3 files changed, 102 insertions(+) create mode 100644 docs/asciidoc/guides/2.0.0-migration/unions.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 30e140f101..4f21a1af3d 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -47,5 +47,6 @@ **** xref:guides/migration-guide/mutations/index.adoc[] *** xref:guides/v2-migration/index.adoc[] **** xref:guides/v2-migration/mutations/index.adoc[] +**** xref:guides/v2-migration/unions/index.adoc[] **** xref:guides/v2-migration/miscellaneous/index.adoc[] ** xref:troubleshooting/index.adoc[] diff --git a/docs/asciidoc/guides/2.0.0-migration/unions.adoc b/docs/asciidoc/guides/2.0.0-migration/unions.adoc new file mode 100644 index 0000000000..3e334f744a --- /dev/null +++ b/docs/asciidoc/guides/2.0.0-migration/unions.adoc @@ -0,0 +1,98 @@ +[[v2-migration-unions]] += Unions + +In this release, we have decided to take the opportunity to overhaul the existing support for unions on relationship fields, to better set us up for adding top-level union support in the future. + +All examples in this section will be based off the following type definitions: + +[source, graphql] +---- +type Actor { + name: String! + actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +type Movie { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} + +type Series { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} + +union Production = Movie | Series +---- + +== Input types + +The structure of input types for union queries and mutations have been changed for user friendliness, and a more consistent API. + +Essentially, field names which were previously of template `${unionFieldName}_${concreteType}` (for example, "actedIn_Movie") are now an object, with the field name at the top, and the member types under it. + +For example, a Mutation which would have previously been: + +[source, graphql] +---- +mutation { + createActors( + input: [ + { + name: "Tom Hiddleston" + actedIn_Movie: { + create: [ + { + title: "The Avengers" + } + ] + } + actedIn_Series: { + create: [ + { + title: "Loki" + } + ] + } + } + ] + ) +} +---- + +Will now be: + +[source, graphql] +---- +mutation { + createActors( + input: [ + { + name: "Tom Hiddleston" + actedIn: { + Movie: { + create: [ + { + node: { + title: "The Avengers" + } + } + ] + } + Series: { + create: [ + { + node: { + title: "Loki" + } + } + ] + } + } + } + ] + ) +} +---- + +Note the change in structure for union input, but also the additional `node` level which enables the use of relationship properties. These changes are consistent across all operations, including `where`. diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 0242a362fc..27947a0de0 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -159,6 +159,9 @@ + + + From fba38ebe266dd8a8d758eb710e5d61e45dd064ba Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 14:45:19 +0100 Subject: [PATCH 03/20] Document union where changes in migration guide --- .../guides/2.0.0-migration/unions.adoc | 38 +++++++++++++++++++ .../unions-and-interfaces.adoc | 1 + 2 files changed, 39 insertions(+) diff --git a/docs/asciidoc/guides/2.0.0-migration/unions.adoc b/docs/asciidoc/guides/2.0.0-migration/unions.adoc index 3e334f744a..1675420a6b 100644 --- a/docs/asciidoc/guides/2.0.0-migration/unions.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/unions.adoc @@ -96,3 +96,41 @@ mutation { ---- Note the change in structure for union input, but also the additional `node` level which enables the use of relationship properties. These changes are consistent across all operations, including `where`. + +== Filtering union fields + +There has been a slight change to how you filter union fields, adding a `where` level above each union member. For example, for a query which would have used to have looked like: + +[source, graphql] +---- +query { + actors { + name + actedIn(Movie: { "The Avengers" }) { + ... on Movie { + title + } + } + } +} +---- + +This will now be written like: + +[source, graphql] +---- +query { + actors { + name + actedIn(where: { Movie: { "The Avengers" }}) { + ... on Movie { + title + } + } + } +} +---- + +We feel this has improved readability of queries. + +Furthermore, the where argument used now dictates which union members are returned from the database, to prevent overfetching. Please see <> for background and explanation of this decision. diff --git a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc index 1980e23cab..16a843783b 100644 --- a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc +++ b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc @@ -29,6 +29,7 @@ type User { Below you can find some examples of how queries and mutations work with this example. +[[type-definitions-unions-and-interfaces-union-types-querying]] === Querying a union Which union members are returned by a Query are dictated by the `where` filter applied. From 6de34c58421980dd15534c38d4d37b6fc0c26a4f Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 14:53:27 +0100 Subject: [PATCH 04/20] Move custom resolvers page down --- docs/antora/content-nav.adoc | 2 +- docs/docbook/content-map.xml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 4f21a1af3d..0d4cce05eb 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -8,7 +8,6 @@ *** xref:type-definitions/autogeneration/index.adoc[] *** xref:type-definitions/cypher/index.adoc[] *** xref:type-definitions/default-values/index.adoc[] -** xref:custom-resolvers/index.adoc[] ** xref:schema/index.adoc[] *** xref:schema/queries/index.adoc[] *** xref:schema/mutations/index.adoc[] @@ -17,6 +16,7 @@ *** xref:schema/pagination/index.adoc[] ** xref:queries/index.adoc[] ** xref:mutations/index.adoc[] +** xref:custom-resolvers/index.adoc[] ** xref:auth/index.adoc[] *** xref:auth/setup/index.adoc[] *** xref:auth/authentication/index.adoc[] diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 27947a0de0..e1da527993 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -32,10 +32,6 @@ - - - - @@ -63,6 +59,10 @@ + + + + From ed72819a893dd2b176e491327595d515fdb1d72d Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 14:55:27 +0100 Subject: [PATCH 05/20] Ensure contents in type definitions map to underlying pages --- docs/asciidoc/type-definitions/index.adoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/asciidoc/type-definitions/index.adoc b/docs/asciidoc/type-definitions/index.adoc index 93eb1127b8..1af466c8dc 100644 --- a/docs/asciidoc/type-definitions/index.adoc +++ b/docs/asciidoc/type-definitions/index.adoc @@ -1,11 +1,13 @@ [[type-definitions]] = Type Definitions +* <> +* <> * <> -* <> -* <> * <> -* <> +* <> +* <> +* <> == Basics From 954f370bcd4cb874d44c64c63c274ed1231de2af Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 17:35:28 +0100 Subject: [PATCH 06/20] Documentation changes from editorial comments, aligning frontpage with other products --- docs/antora/content-nav.adoc | 3 +- docs/asciidoc/api-reference.adoc | 2 +- docs/asciidoc/driver-and-config.adoc | 6 +- .../guides/migration-guide/server.adoc | 2 +- docs/asciidoc/index.adoc | 61 ++++++------------- docs/asciidoc/introduction.adoc | 39 ++++++++++++ docs/docbook/content-map.xml | 8 ++- 7 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 docs/asciidoc/introduction.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 0d4cce05eb..f4d43d3a56 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -1,4 +1,5 @@ * xref:index.adoc[] +** xref:introduction/index.adoc[] ** xref:getting-started/index.adoc[] ** xref:type-definitions/index.adoc[] *** xref:type-definitions/types/index.adoc[] @@ -38,7 +39,7 @@ *** xref:ogm/private/index.adoc[] *** xref:ogm/selection-set/index.adoc[] *** xref:ogm/api-reference/index.adoc[] -** xref:drivers-and-config/index.adoc[] +** xref:driver-configuration/index.adoc[] ** xref:guides/index.adoc[] *** xref:guides/migration-guide/index.adoc[] **** xref:guides/migration-guide/server/index.adoc[] diff --git a/docs/asciidoc/api-reference.adoc b/docs/asciidoc/api-reference.adoc index 6205816e1e..dcb8c52be5 100644 --- a/docs/asciidoc/api-reference.adoc +++ b/docs/asciidoc/api-reference.adoc @@ -35,7 +35,7 @@ const neo4jGraphQL = new Neo4jGraphQL({ === Methods ==== `checkNeo4jCompat` -Reference: <> +Reference: <> == `OGM` Reference: <> diff --git a/docs/asciidoc/driver-and-config.adoc b/docs/asciidoc/driver-and-config.adoc index 593782b3ab..b3bfba7f07 100644 --- a/docs/asciidoc/driver-and-config.adoc +++ b/docs/asciidoc/driver-and-config.adoc @@ -1,4 +1,4 @@ -[[drivers-and-config]] +[[driver-configuration]] = Driver Configuration @@ -50,8 +50,8 @@ const driver = neo4j.driver( const ogm = new OGM({ typeDefs, driver }); ---- -[[drivers-and-config-checkNeo4jCompat]] -== `checkNeo4jCompat` +[[driver-configuration-database-compatibility]] +== Database Compatibility Use the `checkNeo4jCompat` method available on either `Neo4jGraphQL` or the `OGM` to ensure the specified DBMS has the required; versions, functions and procedures. ==== `Neo4jGraphQL` diff --git a/docs/asciidoc/guides/migration-guide/server.adoc b/docs/asciidoc/guides/migration-guide/server.adoc index 0b9a256126..2f1ebea082 100644 --- a/docs/asciidoc/guides/migration-guide/server.adoc +++ b/docs/asciidoc/guides/migration-guide/server.adoc @@ -84,4 +84,4 @@ server.listen().then(({ url }) => { }); ---- -Database bookmarks are also supported. See <> for more information. +Database bookmarks are also supported. See <> for more information. diff --git a/docs/asciidoc/index.adoc b/docs/asciidoc/index.adoc index 7922ddb23b..96d8b38282 100644 --- a/docs/asciidoc/index.adoc +++ b/docs/asciidoc/index.adoc @@ -18,44 +18,23 @@ ifdef::backend-pdf[] Documentation license: <> endif::[] - -Welcome to the official documentation for the Neo4j GraphQL Library. - -== Introduction - -In this section you will find; notes on background information, links and resources, dependencies and requirements, plus pointers on where to get support. If you are already familiar with Neo4j GraphQL, or just want to get started, then jump directly to <>. - -== Why Neo4j and GraphQL? - -Bringing the native graph storage of Neo4j to the GraphQL ecosystem has been a long time in the making. Translating your GraphQL queries to Cypher that the Neo4j database can understand means that we can easily eliminate the n+1 issue that plagues a lot of GraphQL implementations. - -== What is Neo4j GraphQL ? - -It is a GraphQL to Cypher query execution layer for Neo4j and **JavaScript** GraphQL implementations. Such an implementation makes it easier for developers to use Neo4j and GraphQL together. - -Taking a set of Type Definitions; we produce an executable GraphQL Schema, where a given GraphQL query is transformed into a single Cypher query. This translation means users can let the library handle all the database communications, and thus enabling users to focus on building great applications. - -Our GraphQL implementation exposes two products; - -1. `Neo4jGraphQL` - Used for GraphQL API's such as Apollo Server -2. <> - A Handy tool, to use in application code, driven by GraphQL Type Definitions. - -With a powerful feature set including; - -1. <> -2. Nested Mutations - -Overall anyone in the Javascript ecosystem, wanting to use either Neo4j and or GraphQL should find Neo4j GraphQL an indispensable tool for building great applications. - -== Requirements -1. https://neo4j.com/[Neo4j Database] 4.1.0 and above -2. https://neo4j.com/developer/neo4j-apoc/[APOC] 4.1.0 and above - -== Resources -1. https://github.com/neo4j/graphql[Github] -2. https://github.com/neo4j/graphql/issues[Bug Tracker] -3. https://www.npmjs.com/package/@neo4j/graphql[NPM] - -== Licence -1. Documentation: link:{common-license-page-uri}[Creative Commons 4.0] -2. Source: https://www.apache.org/licenses/LICENSE-2.0[Apache 2.0] +> This is the documentation for the Neo4j GraphQL Library version 2.0, authored by the Neo4j GraphQL Team. + +This documentation covers the following topics: + +- <> - Introduction to the Neo4j GraphQL Library. +- <> - Start here if you want to quickly get up and running with the library. +- <> - Define your nodes and relationships using type definitions as documented here. +- <> - Interacting with the schema produced by the Neo4j GraphQL Library. +- <> - GraphQL Queries allow you to read data in your Neo4j database. +- <> - GraphQL Mutations allow you to change data in your Neo4j database. +- <> - Learn how to implement custom functionality accessible through your API. +- <> - Covers the authentication and authorisation options offered by this library. +- <> - An index of all of the directives offered by the Neo4j GraphQL Library. +- <> +- <> +- <> - How to configure a database driver for use with this library. +- <> - Guides for usage of the library, including migration guides for moving between versions. +- <> - Having problems with the library? See if your problem has been found and solved before. + +This manual is primarily written for software engineers building an API using the Neo4j GraphQL Library. diff --git a/docs/asciidoc/introduction.adoc b/docs/asciidoc/introduction.adoc new file mode 100644 index 0000000000..a92d5f2b69 --- /dev/null +++ b/docs/asciidoc/introduction.adoc @@ -0,0 +1,39 @@ +[[introduction]] += Introduction + +In this section you will find; notes on background information, links and resources, dependencies and requirements, plus pointers on where to get support. If you are already familiar with Neo4j GraphQL, or just want to get started, then jump directly to <>. + +== Why Neo4j and GraphQL? + +Bringing the native graph storage of Neo4j to the GraphQL ecosystem has been a long time in the making. Translating your GraphQL queries to Cypher that the Neo4j database can understand means that we can easily eliminate the n+1 issue that plagues a lot of GraphQL implementations. + +== What is Neo4j GraphQL ? + +It is a GraphQL to Cypher query execution layer for Neo4j and **JavaScript** GraphQL implementations. Such an implementation makes it easier for developers to use Neo4j and GraphQL together. + +Taking a set of Type Definitions; we produce an executable GraphQL Schema, where a given GraphQL query is transformed into a single Cypher query. This translation means users can let the library handle all the database communications, and thus enabling users to focus on building great applications. + +Our GraphQL implementation exposes two products; + +1. `Neo4jGraphQL` - Used for GraphQL API's such as Apollo Server +2. <> - A Handy tool, to use in application code, driven by GraphQL Type Definitions. + +With a powerful feature set including; + +1. <> +2. Nested Mutations + +Overall anyone in the Javascript ecosystem, wanting to use either Neo4j and or GraphQL should find Neo4j GraphQL an indispensable tool for building great applications. + +== Requirements +1. https://neo4j.com/[Neo4j Database] 4.1.0 and above +2. https://neo4j.com/developer/neo4j-apoc/[APOC] 4.1.0 and above + +== Resources +1. https://github.com/neo4j/graphql[Github] +2. https://github.com/neo4j/graphql/issues[Bug Tracker] +3. https://www.npmjs.com/package/@neo4j/graphql[NPM] + +== Licence +1. Documentation: link:{common-license-page-uri}[Creative Commons 4.0] +2. Source: https://www.apache.org/licenses/LICENSE-2.0[Apache 2.0] diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index e1da527993..1b431ada0a 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -3,6 +3,10 @@ + + + + @@ -133,8 +137,8 @@ - - + + From 1dd84903398b451356a8571f12af8d6a80e13e3f Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 17:38:51 +0100 Subject: [PATCH 07/20] Missing summaries in contents --- docs/asciidoc/index.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/asciidoc/index.adoc b/docs/asciidoc/index.adoc index 96d8b38282..b817280ba7 100644 --- a/docs/asciidoc/index.adoc +++ b/docs/asciidoc/index.adoc @@ -31,8 +31,8 @@ This documentation covers the following topics: - <> - Learn how to implement custom functionality accessible through your API. - <> - Covers the authentication and authorisation options offered by this library. - <> - An index of all of the directives offered by the Neo4j GraphQL Library. -- <> -- <> +- <> - API reference for constructing an instance of the library. +- <> - This chapter covers the OGM (Object Graph Mapper), a programmatic way of using your API. - <> - How to configure a database driver for use with this library. - <> - Guides for usage of the library, including migration guides for moving between versions. - <> - Having problems with the library? See if your problem has been found and solved before. From 603d2bc11c9f2333969071e103650f57216ab1e1 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 17:55:22 +0100 Subject: [PATCH 08/20] Restructure following editorial comments --- docs/antora/content-nav.adoc | 11 +- docs/asciidoc/{schema => }/filtering.adoc | 6 +- .../guides/2.0.0-migration/mutations.adoc | 2 +- .../guides/migration-guide/mutations.adoc | 2 +- docs/asciidoc/index.adoc | 4 +- docs/asciidoc/mutations.adoc | 292 ++++++++++++++++- docs/asciidoc/ogm/methods/count.adoc | 2 +- docs/asciidoc/ogm/methods/create.adoc | 4 +- docs/asciidoc/ogm/methods/delete.adoc | 6 +- docs/asciidoc/ogm/methods/find.adoc | 4 +- docs/asciidoc/ogm/methods/update.adoc | 18 +- docs/asciidoc/pagination/cursor-based.adoc | 4 + docs/asciidoc/pagination/index.adoc | 7 + .../offset-based.adoc} | 14 +- docs/asciidoc/queries.adoc | 81 ++++- docs/asciidoc/schema/index.adoc | 10 - docs/asciidoc/schema/mutations.adoc | 294 ------------------ docs/asciidoc/schema/queries.adoc | 83 ----- docs/asciidoc/{schema => }/sorting.adoc | 2 +- docs/asciidoc/type-definitions/types.adoc | 6 +- docs/docbook/content-map.xml | 37 ++- 21 files changed, 438 insertions(+), 451 deletions(-) rename docs/asciidoc/{schema => }/filtering.adoc (93%) create mode 100644 docs/asciidoc/pagination/cursor-based.adoc create mode 100644 docs/asciidoc/pagination/index.adoc rename docs/asciidoc/{schema/pagination.adoc => pagination/offset-based.adoc} (85%) delete mode 100644 docs/asciidoc/schema/index.adoc delete mode 100644 docs/asciidoc/schema/mutations.adoc delete mode 100644 docs/asciidoc/schema/queries.adoc rename docs/asciidoc/{schema => }/sorting.adoc (98%) diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index f4d43d3a56..6dc1d50a8f 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -9,14 +9,13 @@ *** xref:type-definitions/autogeneration/index.adoc[] *** xref:type-definitions/cypher/index.adoc[] *** xref:type-definitions/default-values/index.adoc[] -** xref:schema/index.adoc[] -*** xref:schema/queries/index.adoc[] -*** xref:schema/mutations/index.adoc[] -*** xref:schema/filtering/index.adoc[] -*** xref:schema/sorting/index.adoc[] -*** xref:schema/pagination/index.adoc[] ** xref:queries/index.adoc[] ** xref:mutations/index.adoc[] +** xref:filtering/index.adoc[] +** xref:sorting/index.adoc[] +** xref:pagination/index.adoc[] +*** xref:pagination/offset-based/index.adoc[] +*** xref:pagination/cursor-based/index.adoc[] ** xref:custom-resolvers/index.adoc[] ** xref:auth/index.adoc[] *** xref:auth/setup/index.adoc[] diff --git a/docs/asciidoc/schema/filtering.adoc b/docs/asciidoc/filtering.adoc similarity index 93% rename from docs/asciidoc/schema/filtering.adoc rename to docs/asciidoc/filtering.adoc index 8b1f2924ee..c6371bd3be 100644 --- a/docs/asciidoc/schema/filtering.adoc +++ b/docs/asciidoc/filtering.adoc @@ -1,4 +1,4 @@ -[[schema-filtering]] +[[filtering]] = Filtering == Operators @@ -9,7 +9,7 @@ When querying for data, a number of operators are available for different types All types can be tested for either equality or non-equality. For the `Boolean` type, these are the only available comparison operators. -[[schema-filtering-numerical-operators]] +[[filtering-numerical-operators]] === Numerical operators The following comparison operators are available for numeric types (`Int`, `Float`, <>), temporal types (<>) and spatial types (<>, <>): @@ -57,7 +57,7 @@ These four operators are available for all types apart from `Boolean`. == Usage -Using the type definitions from <>, below are some example of how filtering can be applied whilst querying for data. +Using the type definitions from <>, below are some example of how filtering can be applied whilst querying for data. === At the root of a Query diff --git a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc index c4ec4eec10..2c06a25a44 100644 --- a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc @@ -114,7 +114,7 @@ mutation { } ---- -Note the additional level "node" before specifying the actor name for the create operation and in the connect where. 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. +Note the additional level "node" before specifying the actor name for the create operation and in the connect where. 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 diff --git a/docs/asciidoc/guides/migration-guide/mutations.adoc b/docs/asciidoc/guides/migration-guide/mutations.adoc index 4f26d4857d..739c73f222 100644 --- a/docs/asciidoc/guides/migration-guide/mutations.adoc +++ b/docs/asciidoc/guides/migration-guide/mutations.adoc @@ -26,7 +26,7 @@ A summary of migration points is as follows: * The object(s) being mutated are returned as a nested field, to allow for metadata about the operation to be added in future * Mutation arguments are now commonly named between different types, but with different input types - such as `where` and `input` -> Note that <> in `@neo4j/graphql` are incredibly powerful, and it is well worthwhile reading about them in full. You might find that you can collapse multiple current mutations down into one! +> Note that <> in `@neo4j/graphql` are incredibly powerful, and it is well worthwhile reading about them in full. You might find that you can collapse multiple current mutations down into one! == Creating diff --git a/docs/asciidoc/index.adoc b/docs/asciidoc/index.adoc index b817280ba7..813a332375 100644 --- a/docs/asciidoc/index.adoc +++ b/docs/asciidoc/index.adoc @@ -25,9 +25,11 @@ This documentation covers the following topics: - <> - Introduction to the Neo4j GraphQL Library. - <> - Start here if you want to quickly get up and running with the library. - <> - Define your nodes and relationships using type definitions as documented here. -- <> - Interacting with the schema produced by the Neo4j GraphQL Library. - <> - GraphQL Queries allow you to read data in your Neo4j database. - <> - GraphQL Mutations allow you to change data in your Neo4j database. +- <> - This chapter covers how to filter your data in Queries and Mutations. +- <> - This chapter covers how to sort the data being returned. +- <> - This chapter covers the pagination options offered by the Neo4j GraphQL Library. - <> - Learn how to implement custom functionality accessible through your API. - <> - Covers the authentication and authorisation options offered by this library. - <> - An index of all of the directives offered by the Neo4j GraphQL Library. diff --git a/docs/asciidoc/mutations.adoc b/docs/asciidoc/mutations.adoc index 222d77f889..890d6c209b 100644 --- a/docs/asciidoc/mutations.adoc +++ b/docs/asciidoc/mutations.adoc @@ -1,4 +1,294 @@ [[mutations]] = Mutations -See <>. +Mutations for create, update and delete operations are automatically generated for each type defined in type definitions. + +This section will briefly go through each Mutation generated, using the following type definitions as an example: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +*A note on nested Mutations* + +> You will see some basic examples of nested Mutations below, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! +> +> However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. +> +> If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. +> +> If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. + +[[mutations-create]] +== Create + +The following create Mutations and response types will be generated for the above type definitions: + +[source, graphql] +---- +type CreatePostsMutationResponse { + posts: [Post!]! +} + +type CreateUsersMutationResponse { + users: [User!]! +} + +type Mutation { + createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! + createUsers(input: [UsersCreateInput!]!): CreateUsersMutationResponse! +} +---- + +The `CreateInput` types closely mirror the object type definitions, allowing you to create not only the type in question, but to recurse down and perform further operations on related types in the same Mutation. + +> The `id` field will be absent from both create input types as the <> directive has been used. + +=== Single create + +A single user can be created by executing the following GraphQL statement: + +[source, graphql] +---- +mutation { + createUsers(input: [ + { + name: "John Doe" + } + ]) { + users { + id + name + } + } +} +---- + +This will create a User with name "John Doe", and that name plus the autogenerated ID will be returned. + +=== Nested create + +A User and an initial Post can be created by executing the following: + +[source, graphql] +---- +mutation { + createUsers(input: [ + { + name: "John Doe" + posts: { + create: [ + { + node: { + content: "Hi, my name is John!" + } + } + ] + } + } + ]) { + users { + id + name + posts { + id + content + } + } + } +} +---- + +This will create a User with name "John Doe", an introductory Post, both of which will be returned with their autogenerated IDs. + +[[mutations-update]] +== Update + +[source, graphql] +---- +type UpdatePostsMutationResponse { + posts: [Post!]! +} + +type UpdateUsersMutationResponse { + users: [User!]! +} + +type Mutation { + updatePosts( + where: PostWhere + update: PostUpdateInput + connect: PostConnectInput + disconnect: PostDisconnectInput + create: PostCreateInput + delete: PostDeleteInput + ): UpdatePostsMutationResponse! + updateUsers( + where: UserWhere + update: UserUpdateInput + connect: UserConnectInput + disconnect: UserDisconnectInput + create: UserCreateInput + delete: UserDeleteInput + ): UpdateUsersMutationResponse! +} +---- + +> The `id` field not be update-able as the <> directive has been used. + +=== Single update + +Say we wanted to edit the content of a Post: + +[source, graphql] +---- +mutation { + updatePosts( + where: { + id: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" + } + update: { + content: "Some new content for this Post!" + } + ) { + posts { + content + } + } +} +---- + +=== Nested update + +Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: + +[source, graphql] +---- +mutation { + updateUsers( + where: { name: "John Doe" } + create: { + posts: [ + { node: { content: "An interesting way of adding a new Post!" } } + ] + } + ) { + users { + id + name + posts { + content + } + } + } +} +---- + +[[mutations-delete]] +== Delete + +The following delete Mutations and response type will be generated for the above type definitions: + +[source, graphql] +---- +type DeleteInfo { + nodesDeleted: Int! + relationshipsDeleted: Int! +} + +type Mutation { + deletePosts(where: PostWhere, delete: PostDeleteInput): DeleteInfo! + deleteUsers(where: UserWhere, delete: UserDeleteInput): DeleteInfo! +} +---- + +Note that the `DeleteInfo` type is the common return type for all delete Mutations. + +=== Single Delete + +A single post can be deleted by executing the following GraphQL statement: + +[source, graphql] +---- +mutation { + deletePosts(where: [ + { + id: "6042E807-47AE-4857-B7FE-1AADF522DE8B" + } + ]) { + nodesDeleted + relationshipsDeleted + } +} +---- + +This will delete the post using the autogenerated ID that would have been returned after that post's creation. + +We would see that `nodesDeleted` would equal 1 (the post) and `relationshipsDeleted` would also equal equal 1 (the `HAS_POST` relationship between the Post and its author). + +=== Nested Delete + +Say that if when we delete a User, we want to delete _all_ of their Posts as well. This can be achieved using a single nested delete operations: + +[source, graphql] +---- +mutation { + deleteUsers( + where: [ + { + name: "Jane Doe" + } + ], + delete: { + posts: [ + where: { } + ] + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +You may look at that empty `where` argument and wonder what that's doing. By the time we reach that argument, we are already only dealing with the posts that were created by Jane Doe, as we traversed the graph to those Post nodes from her User node. Essentially, the above query is equivalent to: + +[source, graphql] +---- +mutation { + deleteUsers( + where: [ + { + name: "Jane Doe" + } + ], + delete: { + posts: [ + where: { + node: { + creator: { + name: "Jane Doe" + } + } + } + ] + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +Slightly easier to reason with, but the output Cypher statement will have a redundant `WHERE` clause! diff --git a/docs/asciidoc/ogm/methods/count.adoc b/docs/asciidoc/ogm/methods/count.adoc index 3d9cd8688d..6211806609 100644 --- a/docs/asciidoc/ogm/methods/count.adoc +++ b/docs/asciidoc/ogm/methods/count.adoc @@ -17,4 +17,4 @@ const usersCount = await User.count(); == Args === `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. +JavaScript object representation of the GraphQL `where` input type, used for <>. diff --git a/docs/asciidoc/ogm/methods/create.adoc b/docs/asciidoc/ogm/methods/create.adoc index db71e8eaba..478d5aae08 100644 --- a/docs/asciidoc/ogm/methods/create.adoc +++ b/docs/asciidoc/ogm/methods/create.adoc @@ -1,7 +1,7 @@ [[ogm-methods-create]] = Create -Use to create many nodes. The `model.create` method maps to the underlying <> operation. +Use to create many nodes. The `model.create` method maps to the underlying <> operation. == Usage [source, javascript] @@ -14,7 +14,7 @@ await Movie.create({ input: [{ title: "The Matrix" }] }) == Args === `input` -JavaScript object representation of the GraphQL `input` input type, used for <>. +JavaScript object representation of the GraphQL `input` input type, used for <>. === `selectionSet` diff --git a/docs/asciidoc/ogm/methods/delete.adoc b/docs/asciidoc/ogm/methods/delete.adoc index e566fb4b43..780fc2e78b 100644 --- a/docs/asciidoc/ogm/methods/delete.adoc +++ b/docs/asciidoc/ogm/methods/delete.adoc @@ -1,7 +1,7 @@ [[ogm-methods-delete]] = Delete -Use to delete many nodes. The `model.delete` method maps to the underlying <> operation. +Use to delete many nodes. The `model.delete` method maps to the underlying <> operation. == Usage [source, javascript] @@ -14,10 +14,10 @@ await User.delete({ where: { name: "dan" }}); == Args === `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. +JavaScript object representation of the GraphQL `where` input type, used for <>. === `delete` -JavaScript object representation of the GraphQL `delete` input type, used for <>. +JavaScript object representation of the GraphQL `delete` input type, used for <>. === `args` The arguments for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/find.adoc b/docs/asciidoc/ogm/methods/find.adoc index 5af3383653..95ffe313a5 100644 --- a/docs/asciidoc/ogm/methods/find.adoc +++ b/docs/asciidoc/ogm/methods/find.adoc @@ -1,7 +1,7 @@ [[ogm-methods-find]] = Find -Use to update many nodes. The `model.find` method maps to the underlying schema <>. +Use to update many nodes. The `model.find` method maps to the underlying schema <>. == Usage @@ -37,7 +37,7 @@ const users = await User.find({ == Args === `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. +JavaScript object representation of the GraphQL `where` input type, used for <>. === `options` JavaScript object representation of the GraphQL `options` input type, used for <>. diff --git a/docs/asciidoc/ogm/methods/update.adoc b/docs/asciidoc/ogm/methods/update.adoc index 46b7213099..0efdf7275d 100644 --- a/docs/asciidoc/ogm/methods/update.adoc +++ b/docs/asciidoc/ogm/methods/update.adoc @@ -1,7 +1,7 @@ [[ogm-methods-update]] = Update -Use to update many nodes. The `model.update` method maps to the underlying <> operation. +Use to update many nodes. The `model.update` method maps to the underlying <> operation. == Usage [source, javascript] @@ -17,29 +17,29 @@ const { users } = await User.update({ == Args === `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. +JavaScript object representation of the GraphQL `where` input type, used for <>. === `update` -JavaScript object representation of the GraphQL `update` input type, used for <>. +JavaScript object representation of the GraphQL `update` input type, used for <>. === `connect` -JavaScript object representation of the GraphQL `connect` input type, used for <>. +JavaScript object representation of the GraphQL `connect` input type, used for <>. === `disconnect` -JavaScript object representation of the GraphQL `disconnect` input type, used for <>. +JavaScript object representation of the GraphQL `disconnect` input type, used for <>. === `create` -JavaScript object representation of the GraphQL `create` input type, used for <>. +JavaScript object representation of the GraphQL `create` input type, used for <>. === `selectionSet` Reference: <> === `args` -The arguments for the GraphQL Query. +The arguments for the GraphQL Query. === `context` -The `context` for the GraphQL Query. +The `context` for the GraphQL Query. === `rootValue` -The `rootValue` for the GraphQL Query. \ No newline at end of file +The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/pagination/cursor-based.adoc b/docs/asciidoc/pagination/cursor-based.adoc new file mode 100644 index 0000000000..c027582cac --- /dev/null +++ b/docs/asciidoc/pagination/cursor-based.adoc @@ -0,0 +1,4 @@ +[[pagination-cursor-based]] += Cursor-based pagination + +On relationship fields, you are able to take advantage of cursor-based pagination. diff --git a/docs/asciidoc/pagination/index.adoc b/docs/asciidoc/pagination/index.adoc new file mode 100644 index 0000000000..a4b09e1e28 --- /dev/null +++ b/docs/asciidoc/pagination/index.adoc @@ -0,0 +1,7 @@ +[[pagination]] += Pagination + +The Neo4j GraphQL Library offers two mechanisms for pagination: + +- <> - Pagination based on offsets, often associated with navigation via pages. +- <> - Pagination based on cursors, often associated with infinitely-scrolling applications. diff --git a/docs/asciidoc/schema/pagination.adoc b/docs/asciidoc/pagination/offset-based.adoc similarity index 85% rename from docs/asciidoc/schema/pagination.adoc rename to docs/asciidoc/pagination/offset-based.adoc index 7f39850d64..091a523fed 100644 --- a/docs/asciidoc/schema/pagination.adoc +++ b/docs/asciidoc/pagination/offset-based.adoc @@ -1,7 +1,5 @@ -[[schema-pagination]] -= Pagination - -== Page-based pagination +[[pagination-offset-based]] += Offset-based pagination Page-based pagination can be achieved through the use of the `offset` and `limit` options available whilst querying for data. @@ -55,11 +53,11 @@ query { } ---- -=== Total number of pages +== Total number of pages You can fetch the total number of records for a certain type using its count query, and then divide that number by your entries per page in order to calculate the total number of pages. This will allow to to determine what the last page is, and whether there is a next page. -=== Paginating relationship fields +== Paginating relationship fields Say that in addition to the `User` type above, there is also a `Post` type which a `User` has many of. You can also fetch a `User` and then paginate through their posts: @@ -79,7 +77,3 @@ query { } } ---- - -== Cursor-based pagination - -On relationship fields, you are able to take advantage of cursor-based pagination. diff --git a/docs/asciidoc/queries.adoc b/docs/asciidoc/queries.adoc index 83dd9e7109..148141707c 100644 --- a/docs/asciidoc/queries.adoc +++ b/docs/asciidoc/queries.adoc @@ -1,4 +1,83 @@ [[queries]] = Queries -See <>. +Each object defined in type definitions will have a Query field generated for it. Each Query field accepts two arguments: + +* `where`: Used to specify the <> which should be applied whilst querying the data +* `options`: Used to specify the options for <> and <> + +== Example + +Given the following type definitions: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following Query fields will be automatically generated: + +[source, graphql] +---- +type Query { + posts(where: PostWhere, options: PostOptions): [Post!]! + users(where: UserWhere, options: UserOptions): [User!]! + countPosts(where: PostWhere): Int! + usersCount(where: UserWhere): Int! +} +---- + +=== Querying for all Users + +The following Query will return all Users, returning their ID and name. + +[source, graphql] +---- +query { + users { + id + name + } +} +---- + +=== Query for all Users' Posts + +The following Query will return all Users, returning the content which they have posted. + +[source, graphql] +---- +query { + users { + posts { + content + } + } +} +---- + + +=== Counting all Users + +The following Query will count all users and return a number. + +[source, graphql] +---- +query { + usersCount +} +---- + +=== Filtering + +See <> for details on how data can be filtered whilst querying. diff --git a/docs/asciidoc/schema/index.adoc b/docs/asciidoc/schema/index.adoc deleted file mode 100644 index 433eceed0f..0000000000 --- a/docs/asciidoc/schema/index.adoc +++ /dev/null @@ -1,10 +0,0 @@ -[[schema]] -= Schema - -In this section you will learn how to use the exposed GraphQL schema to interact with the Neo4j database. - -* <> -* <> -* <> -* <> -* <> diff --git a/docs/asciidoc/schema/mutations.adoc b/docs/asciidoc/schema/mutations.adoc deleted file mode 100644 index 8d00c3b6b6..0000000000 --- a/docs/asciidoc/schema/mutations.adoc +++ /dev/null @@ -1,294 +0,0 @@ -[[schema-mutations]] -= Mutations - -Mutations for create, update and delete operations are automatically generated for each type defined in type definitions. - -This section will briefly go through each Mutation generated, using the following type definitions as an example: - -[source, graphql] ----- -type Post { - id: ID! @id - content: String! - creator: User @relationship(type: "HAS_POST", direction: IN) -} - -type User { - id: ID! @id - name: String - posts: [Post] @relationship(type: "HAS_POST", direction: OUT) -} ----- - -*A note on nested Mutations* - -> You will see some basic examples of nested Mutations below, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! -> -> However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. -> -> If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. -> -> If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. - -[[schema-mutations-create]] -== Create - -The following create Mutations and response types will be generated for the above type definitions: - -[source, graphql] ----- -type CreatePostsMutationResponse { - posts: [Post!]! -} - -type CreateUsersMutationResponse { - users: [User!]! -} - -type Mutation { - createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! - createUsers(input: [UsersCreateInput!]!): CreateUsersMutationResponse! -} ----- - -The `CreateInput` types closely mirror the object type definitions, allowing you to create not only the type in question, but to recurse down and perform further operations on related types in the same Mutation. - -> The `id` field will be absent from both create input types as the <> directive has been used. - -=== Single create - -A single user can be created by executing the following GraphQL statement: - -[source, graphql] ----- -mutation { - createUsers(input: [ - { - name: "John Doe" - } - ]) { - users { - id - name - } - } -} ----- - -This will create a User with name "John Doe", and that name plus the autogenerated ID will be returned. - -=== Nested create - -A User and an initial Post can be created by executing the following: - -[source, graphql] ----- -mutation { - createUsers(input: [ - { - name: "John Doe" - posts: { - create: [ - { - node: { - content: "Hi, my name is John!" - } - } - ] - } - } - ]) { - users { - id - name - posts { - id - content - } - } - } -} ----- - -This will create a User with name "John Doe", an introductory Post, both of which will be returned with their autogenerated IDs. - -[[schema-mutations-update]] -== Update - -[source, graphql] ----- -type UpdatePostsMutationResponse { - posts: [Post!]! -} - -type UpdateUsersMutationResponse { - users: [User!]! -} - -type Mutation { - updatePosts( - where: PostWhere - update: PostUpdateInput - connect: PostConnectInput - disconnect: PostDisconnectInput - create: PostCreateInput - delete: PostDeleteInput - ): UpdatePostsMutationResponse! - updateUsers( - where: UserWhere - update: UserUpdateInput - connect: UserConnectInput - disconnect: UserDisconnectInput - create: UserCreateInput - delete: UserDeleteInput - ): UpdateUsersMutationResponse! -} ----- - -> The `id` field not be update-able as the <> directive has been used. - -=== Single update - -Say we wanted to edit the content of a Post: - -[source, graphql] ----- -mutation { - updatePosts( - where: { - id: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" - } - update: { - content: "Some new content for this Post!" - } - ) { - posts { - content - } - } -} ----- - -=== Nested update - -Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: - -[source, graphql] ----- -mutation { - updateUsers( - where: { name: "John Doe" } - create: { - posts: [ - { node: { content: "An interesting way of adding a new Post!" } } - ] - } - ) { - users { - id - name - posts { - content - } - } - } -} ----- - -[[schema-mutations-delete]] -== Delete - -The following delete Mutations and response type will be generated for the above type definitions: - -[source, graphql] ----- -type DeleteInfo { - nodesDeleted: Int! - relationshipsDeleted: Int! -} - -type Mutation { - deletePosts(where: PostWhere, delete: PostDeleteInput): DeleteInfo! - deleteUsers(where: UserWhere, delete: UserDeleteInput): DeleteInfo! -} ----- - -Note that the `DeleteInfo` type is the common return type for all delete Mutations. - -=== Single Delete - -A single post can be deleted by executing the following GraphQL statement: - -[source, graphql] ----- -mutation { - deletePosts(where: [ - { - id: "6042E807-47AE-4857-B7FE-1AADF522DE8B" - } - ]) { - nodesDeleted - relationshipsDeleted - } -} ----- - -This will delete the post using the autogenerated ID that would have been returned after that post's creation. - -We would see that `nodesDeleted` would equal 1 (the post) and `relationshipsDeleted` would also equal equal 1 (the `HAS_POST` relationship between the Post and its author). - -=== Nested Delete - -Say that if when we delete a User, we want to delete _all_ of their Posts as well. This can be achieved using a single nested delete operations: - -[source, graphql] ----- -mutation { - deleteUsers( - where: [ - { - name: "Jane Doe" - } - ], - delete: { - posts: [ - where: { } - ] - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -You may look at that empty `where` argument and wonder what that's doing. By the time we reach that argument, we are already only dealing with the posts that were created by Jane Doe, as we traversed the graph to those Post nodes from her User node. Essentially, the above query is equivalent to: - -[source, graphql] ----- -mutation { - deleteUsers( - where: [ - { - name: "Jane Doe" - } - ], - delete: { - posts: [ - where: { - node: { - creator: { - name: "Jane Doe" - } - } - } - ] - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -Slightly easier to reason with, but the output Cypher statement will have a redundant `WHERE` clause! diff --git a/docs/asciidoc/schema/queries.adoc b/docs/asciidoc/schema/queries.adoc deleted file mode 100644 index e11f9023b3..0000000000 --- a/docs/asciidoc/schema/queries.adoc +++ /dev/null @@ -1,83 +0,0 @@ -[[schema-queries]] -= Queries - -Each object defined in type definitions will have a Query field generated for it. Each Query field accepts two arguments: - -* `where`: Used to specify the <> which should be applied whilst querying the data -* `options`: Used to specify the options for <> and <> - -== Example - -Given the following type definitions: - -[source, graphql] ----- -type Post { - id: ID! @id - content: String! - creator: User @relationship(type: "HAS_POST", direction: IN) -} - -type User { - id: ID! @id - name: String - posts: [Post] @relationship(type: "HAS_POST", direction: OUT) -} ----- - -The following Query fields will be automatically generated: - -[source, graphql] ----- -type Query { - posts(where: PostWhere, options: PostOptions): [Post!]! - users(where: UserWhere, options: UserOptions): [User!]! - countPosts(where: PostWhere): Int! - usersCount(where: UserWhere): Int! -} ----- - -=== Querying for all Users - -The following Query will return all Users, returning their ID and name. - -[source, graphql] ----- -query { - users { - id - name - } -} ----- - -=== Query for all Users' Posts - -The following Query will return all Users, returning the content which they have posted. - -[source, graphql] ----- -query { - users { - posts { - content - } - } -} ----- - - -=== Counting all Users - -The following Query will count all users and return a number. - -[source, graphql] ----- -query { - usersCount -} ----- - -=== Filtering - -See <> for details on how data can be filtered whilst querying. diff --git a/docs/asciidoc/schema/sorting.adoc b/docs/asciidoc/sorting.adoc similarity index 98% rename from docs/asciidoc/schema/sorting.adoc rename to docs/asciidoc/sorting.adoc index 2e7147f561..b623542b3a 100644 --- a/docs/asciidoc/schema/sorting.adoc +++ b/docs/asciidoc/sorting.adoc @@ -1,4 +1,4 @@ -[[schema-sorting]] +[[sorting]] = Sorting A sorting input type is generated for every object type defined in your type definitions, allowing for Query results to be sorted by each individual field. diff --git a/docs/asciidoc/type-definitions/types.adoc b/docs/asciidoc/type-definitions/types.adoc index b077b97889..6764e600b3 100644 --- a/docs/asciidoc/type-definitions/types.adoc +++ b/docs/asciidoc/type-definitions/types.adoc @@ -27,7 +27,7 @@ type Movie { [[type-definitions-types-bigint]] == `BigInt` -Supports up to 64 bit integers, serialized as strings in variables and in data responses. Shares the same <> as the other numeric types. +Supports up to 64 bit integers, serialized as strings in variables and in data responses. Shares the same <> as the other numeric types. [source, graphql] ---- @@ -132,7 +132,7 @@ mutation CreateUsers($name: String!, $longitude: Float!, $latitude: Float!) { ==== Filtering -In addition to the <>, the `Point` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: +In addition to the <>, the `Point` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: [source, graphql] ---- @@ -209,7 +209,7 @@ input CartesianPointInput { ==== Filtering -In addition to the <>, the `CartesianPoint` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: +In addition to the <>, the `CartesianPoint` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: [source, graphql] ---- diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 1b431ada0a..4ac5e58127 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -36,25 +36,6 @@ - - - - - - - - - - - - - - - - - - - @@ -63,6 +44,24 @@ + + + + + + + + + + + + + + + + + + From d1cc0bd4b8a046c8bd22359429b921f05553d824 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 28 Jul 2021 18:21:25 +0100 Subject: [PATCH 09/20] Split out each Mutation operation into separate chapters --- docs/antora/content-nav.adoc | 3 + docs/asciidoc/mutations.adoc | 294 ---------------------------- docs/asciidoc/mutations/create.adoc | 98 ++++++++++ docs/asciidoc/mutations/delete.adoc | 114 +++++++++++ docs/asciidoc/mutations/index.adoc | 18 ++ docs/asciidoc/mutations/update.adoc | 101 ++++++++++ docs/docbook/content-map.xml | 13 ++ 7 files changed, 347 insertions(+), 294 deletions(-) delete mode 100644 docs/asciidoc/mutations.adoc create mode 100644 docs/asciidoc/mutations/create.adoc create mode 100644 docs/asciidoc/mutations/delete.adoc create mode 100644 docs/asciidoc/mutations/index.adoc create mode 100644 docs/asciidoc/mutations/update.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 6dc1d50a8f..6420239f39 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -11,6 +11,9 @@ *** xref:type-definitions/default-values/index.adoc[] ** xref:queries/index.adoc[] ** xref:mutations/index.adoc[] +*** xref:mutations/create/index.adoc[] +*** xref:mutations/update/index.adoc[] +*** xref:mutations/delete/index.adoc[] ** xref:filtering/index.adoc[] ** xref:sorting/index.adoc[] ** xref:pagination/index.adoc[] diff --git a/docs/asciidoc/mutations.adoc b/docs/asciidoc/mutations.adoc deleted file mode 100644 index 890d6c209b..0000000000 --- a/docs/asciidoc/mutations.adoc +++ /dev/null @@ -1,294 +0,0 @@ -[[mutations]] -= Mutations - -Mutations for create, update and delete operations are automatically generated for each type defined in type definitions. - -This section will briefly go through each Mutation generated, using the following type definitions as an example: - -[source, graphql] ----- -type Post { - id: ID! @id - content: String! - creator: User @relationship(type: "HAS_POST", direction: IN) -} - -type User { - id: ID! @id - name: String - posts: [Post] @relationship(type: "HAS_POST", direction: OUT) -} ----- - -*A note on nested Mutations* - -> You will see some basic examples of nested Mutations below, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! -> -> However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. -> -> If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. -> -> If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. - -[[mutations-create]] -== Create - -The following create Mutations and response types will be generated for the above type definitions: - -[source, graphql] ----- -type CreatePostsMutationResponse { - posts: [Post!]! -} - -type CreateUsersMutationResponse { - users: [User!]! -} - -type Mutation { - createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! - createUsers(input: [UsersCreateInput!]!): CreateUsersMutationResponse! -} ----- - -The `CreateInput` types closely mirror the object type definitions, allowing you to create not only the type in question, but to recurse down and perform further operations on related types in the same Mutation. - -> The `id` field will be absent from both create input types as the <> directive has been used. - -=== Single create - -A single user can be created by executing the following GraphQL statement: - -[source, graphql] ----- -mutation { - createUsers(input: [ - { - name: "John Doe" - } - ]) { - users { - id - name - } - } -} ----- - -This will create a User with name "John Doe", and that name plus the autogenerated ID will be returned. - -=== Nested create - -A User and an initial Post can be created by executing the following: - -[source, graphql] ----- -mutation { - createUsers(input: [ - { - name: "John Doe" - posts: { - create: [ - { - node: { - content: "Hi, my name is John!" - } - } - ] - } - } - ]) { - users { - id - name - posts { - id - content - } - } - } -} ----- - -This will create a User with name "John Doe", an introductory Post, both of which will be returned with their autogenerated IDs. - -[[mutations-update]] -== Update - -[source, graphql] ----- -type UpdatePostsMutationResponse { - posts: [Post!]! -} - -type UpdateUsersMutationResponse { - users: [User!]! -} - -type Mutation { - updatePosts( - where: PostWhere - update: PostUpdateInput - connect: PostConnectInput - disconnect: PostDisconnectInput - create: PostCreateInput - delete: PostDeleteInput - ): UpdatePostsMutationResponse! - updateUsers( - where: UserWhere - update: UserUpdateInput - connect: UserConnectInput - disconnect: UserDisconnectInput - create: UserCreateInput - delete: UserDeleteInput - ): UpdateUsersMutationResponse! -} ----- - -> The `id` field not be update-able as the <> directive has been used. - -=== Single update - -Say we wanted to edit the content of a Post: - -[source, graphql] ----- -mutation { - updatePosts( - where: { - id: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" - } - update: { - content: "Some new content for this Post!" - } - ) { - posts { - content - } - } -} ----- - -=== Nested update - -Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: - -[source, graphql] ----- -mutation { - updateUsers( - where: { name: "John Doe" } - create: { - posts: [ - { node: { content: "An interesting way of adding a new Post!" } } - ] - } - ) { - users { - id - name - posts { - content - } - } - } -} ----- - -[[mutations-delete]] -== Delete - -The following delete Mutations and response type will be generated for the above type definitions: - -[source, graphql] ----- -type DeleteInfo { - nodesDeleted: Int! - relationshipsDeleted: Int! -} - -type Mutation { - deletePosts(where: PostWhere, delete: PostDeleteInput): DeleteInfo! - deleteUsers(where: UserWhere, delete: UserDeleteInput): DeleteInfo! -} ----- - -Note that the `DeleteInfo` type is the common return type for all delete Mutations. - -=== Single Delete - -A single post can be deleted by executing the following GraphQL statement: - -[source, graphql] ----- -mutation { - deletePosts(where: [ - { - id: "6042E807-47AE-4857-B7FE-1AADF522DE8B" - } - ]) { - nodesDeleted - relationshipsDeleted - } -} ----- - -This will delete the post using the autogenerated ID that would have been returned after that post's creation. - -We would see that `nodesDeleted` would equal 1 (the post) and `relationshipsDeleted` would also equal equal 1 (the `HAS_POST` relationship between the Post and its author). - -=== Nested Delete - -Say that if when we delete a User, we want to delete _all_ of their Posts as well. This can be achieved using a single nested delete operations: - -[source, graphql] ----- -mutation { - deleteUsers( - where: [ - { - name: "Jane Doe" - } - ], - delete: { - posts: [ - where: { } - ] - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -You may look at that empty `where` argument and wonder what that's doing. By the time we reach that argument, we are already only dealing with the posts that were created by Jane Doe, as we traversed the graph to those Post nodes from her User node. Essentially, the above query is equivalent to: - -[source, graphql] ----- -mutation { - deleteUsers( - where: [ - { - name: "Jane Doe" - } - ], - delete: { - posts: [ - where: { - node: { - creator: { - name: "Jane Doe" - } - } - } - ] - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -Slightly easier to reason with, but the output Cypher statement will have a redundant `WHERE` clause! diff --git a/docs/asciidoc/mutations/create.adoc b/docs/asciidoc/mutations/create.adoc new file mode 100644 index 0000000000..af059dc8c0 --- /dev/null +++ b/docs/asciidoc/mutations/create.adoc @@ -0,0 +1,98 @@ +[[mutations-create]] += Create + +Using the following type definitions for these examples: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following create Mutations and response types will be generated for the above type definitions: + +[source, graphql] +---- +type CreatePostsMutationResponse { + posts: [Post!]! +} + +type CreateUsersMutationResponse { + users: [User!]! +} + +type Mutation { + createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! + createUsers(input: [UsersCreateInput!]!): CreateUsersMutationResponse! +} +---- + +The `CreateInput` types closely mirror the object type definitions, allowing you to create not only the type in question, but to recurse down and perform further operations on related types in the same Mutation. + +> The `id` field will be absent from both create input types as the <> directive has been used. + +== Single create + +A single user can be created by executing the following GraphQL statement: + +[source, graphql] +---- +mutation { + createUsers(input: [ + { + name: "John Doe" + } + ]) { + users { + id + name + } + } +} +---- + +This will create a User with name "John Doe", and that name plus the autogenerated ID will be returned. + +== Nested create + +A User and an initial Post can be created by executing the following: + +[source, graphql] +---- +mutation { + createUsers(input: [ + { + name: "John Doe" + posts: { + create: [ + { + node: { + content: "Hi, my name is John!" + } + } + ] + } + } + ]) { + users { + id + name + posts { + id + content + } + } + } +} +---- + +This will create a User with name "John Doe", an introductory Post, both of which will be returned with their autogenerated IDs. diff --git a/docs/asciidoc/mutations/delete.adoc b/docs/asciidoc/mutations/delete.adoc new file mode 100644 index 0000000000..1f60ba99f9 --- /dev/null +++ b/docs/asciidoc/mutations/delete.adoc @@ -0,0 +1,114 @@ +[[mutations-delete]] += Delete + +Using the following type definitions for these examples: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following delete Mutations and response type will be generated for the above type definitions: + +[source, graphql] +---- +type DeleteInfo { + nodesDeleted: Int! + relationshipsDeleted: Int! +} + +type Mutation { + deletePosts(where: PostWhere, delete: PostDeleteInput): DeleteInfo! + deleteUsers(where: UserWhere, delete: UserDeleteInput): DeleteInfo! +} +---- + +Note that the `DeleteInfo` type is the common return type for all delete Mutations. + +== Single Delete + +A single post can be deleted by executing the following GraphQL statement: + +[source, graphql] +---- +mutation { + deletePosts(where: [ + { + id: "6042E807-47AE-4857-B7FE-1AADF522DE8B" + } + ]) { + nodesDeleted + relationshipsDeleted + } +} +---- + +This will delete the post using the autogenerated ID that would have been returned after that post's creation. + +We would see that `nodesDeleted` would equal 1 (the post) and `relationshipsDeleted` would also equal equal 1 (the `HAS_POST` relationship between the Post and its author). + +== Nested Delete + +Say that if when we delete a User, we want to delete _all_ of their Posts as well. This can be achieved using a single nested delete operations: + +[source, graphql] +---- +mutation { + deleteUsers( + where: [ + { + name: "Jane Doe" + } + ], + delete: { + posts: [ + where: { } + ] + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +You may look at that empty `where` argument and wonder what that's doing. By the time we reach that argument, we are already only dealing with the posts that were created by Jane Doe, as we traversed the graph to those Post nodes from her User node. Essentially, the above query is equivalent to: + +[source, graphql] +---- +mutation { + deleteUsers( + where: [ + { + name: "Jane Doe" + } + ], + delete: { + posts: [ + where: { + node: { + creator: { + name: "Jane Doe" + } + } + } + ] + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +Slightly easier to reason with, but the output Cypher statement will have a redundant `WHERE` clause! diff --git a/docs/asciidoc/mutations/index.adoc b/docs/asciidoc/mutations/index.adoc new file mode 100644 index 0000000000..16729c9b67 --- /dev/null +++ b/docs/asciidoc/mutations/index.adoc @@ -0,0 +1,18 @@ +[[mutations]] += Mutations + +Several Mutations are automatically generated for each type defined in type definitions, which are covered in the following chapters: + +- <> +- <> +- <> + +*A note on nested Mutations* + +> You will see some basic examples of nested Mutations below, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! +> +> However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. +> +> If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. +> +> If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. diff --git a/docs/asciidoc/mutations/update.adoc b/docs/asciidoc/mutations/update.adoc new file mode 100644 index 0000000000..ac4fb8aea3 --- /dev/null +++ b/docs/asciidoc/mutations/update.adoc @@ -0,0 +1,101 @@ +[[mutations-update]] += Update + +Using the following type definitions for these examples: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following update Mutations and response types will be generated for the above type definitions: + +[source, graphql] +---- +type UpdatePostsMutationResponse { + posts: [Post!]! +} + +type UpdateUsersMutationResponse { + users: [User!]! +} + +type Mutation { + updatePosts( + where: PostWhere + update: PostUpdateInput + connect: PostConnectInput + disconnect: PostDisconnectInput + create: PostCreateInput + delete: PostDeleteInput + ): UpdatePostsMutationResponse! + updateUsers( + where: UserWhere + update: UserUpdateInput + connect: UserConnectInput + disconnect: UserDisconnectInput + create: UserCreateInput + delete: UserDeleteInput + ): UpdateUsersMutationResponse! +} +---- + +> The `id` field not be update-able as the <> directive has been used. + +== Single update + +Say we wanted to edit the content of a Post: + +[source, graphql] +---- +mutation { + updatePosts( + where: { + id: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" + } + update: { + content: "Some new content for this Post!" + } + ) { + posts { + content + } + } +} +---- + +== Nested update + +Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: + +[source, graphql] +---- +mutation { + updateUsers( + where: { name: "John Doe" } + create: { + posts: [ + { node: { content: "An interesting way of adding a new Post!" } } + ] + } + ) { + users { + id + name + posts { + content + } + } + } +} +---- diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 4ac5e58127..6248463672 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -44,6 +44,19 @@ + + + + + + + + + + + + + From de67f230c1d6df53606de29fb13763f4ef13c663 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 11:02:14 +0100 Subject: [PATCH 10/20] Rewrite of getting started guide, with more examples and screenshots --- docs/asciidoc/getting-started.adoc | 206 +++++++++++-- docs/images/apollo-server-landing-page.png | Bin 0 -> 89111 bytes docs/images/first-mutation.png | Bin 0 -> 81594 bytes docs/images/first-query.png | Bin 0 -> 64460 bytes packages/graphql/package.json | 2 +- yarn.lock | 343 +++++++++++++++++++-- 6 files changed, 502 insertions(+), 49 deletions(-) create mode 100644 docs/images/apollo-server-landing-page.png create mode 100644 docs/images/first-mutation.png create mode 100644 docs/images/first-query.png diff --git a/docs/asciidoc/getting-started.adoc b/docs/asciidoc/getting-started.adoc index d6bff53064..d1c6df4166 100644 --- a/docs/asciidoc/getting-started.adoc +++ b/docs/asciidoc/getting-started.adoc @@ -1,79 +1,225 @@ [[getting-started]] = Getting Started -This section will help users get started with Neo4j GraphQL. Before starting we recommend readers have an understanding of the following **prerequisites;** +This tutorial walks you through: -1. https://developer.mozilla.org/en-US/docs/Web/JavaScript[JavaScript] -2. https://nodejs.org/en/[Node.js] -3. https://graphql.org/[GraphQL] -4. https://neo4j.com/[Neo4j] +- Installing the Neo4j GraphQL Library and its dependencies +- Defining type definitions that represent the structure of your graph database +- Instantiating an instance of the library, which will generate a GraphQL schema +- Running an instance of a server which will let you execute queries and mutations against your schema -== Installation +This tutorial assumes familiarity with the command line and JavaScript, and also that you have a recent version of Node.js installed. These examples will use the default `npm` package manager, but feel free to use your package manager of choice. + +> This tutorial walks through creating a new project with the Neo4j GraphQL Library. If you are not familiar, it will be worthwhile reading up on https://neo4j.com/[Neo4j] and https://graphql.org/[GraphQL]. + +== Step 1: Create a new project + +1. Create a new directory and `cd` into it: [source, bash] ---- -$ npm install @neo4j/graphql +mkdir neo4j-graphql-example +cd neo4j-graphql-example ---- -`graphql` and `neo4j-driver` are **peerDependencies**, so unless the project already has them installed they need to be installed as well. - -== Requiring - -The Library is transpiled, from our TypeScript source, into https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules[Common JS] - This means you can use the `require` or the `import` syntax to access the library's exported members, depending on your environment and tooling setup; +2. Create a new Node.js project: -[source, javascript] +[source, bash] ---- -const { Neo4jGraphQL } = require("@neo4j/graphql"); +npm init --yes ---- -or +Whilst we're here, let's create an empty `index.js` file which will contain all of the code for this example: -[source, javascript] +[source, bash] ---- -import { Neo4jGraphQL } from "@neo4j/graphql"; +touch index.js ---- -== GraphQL API Quick Start +== Step 2: Install dependencies + +The Neo4j GraphQL Library and it's dependencies must be installed: + +- `@neo4j/graphql` is the official Neo4j GraphQL Library package, which takes your GraphQL type definitions and generates a schema backed by a Neo4j database for you. +- `graphql` is the package used by the Neo4j GraphQL Library to generate a schema and execute queries and mutations. +- `neo4j-driver` is the official Neo4j Driver package for JavaScript, of which an instance must be passed into the Neo4j GraphQL Library. + +Additionally, we will need to install a GraphQL server package which will host our schema and allow us to execute queries and mutations against it. We will use the popular https://www.apollographql.com/docs/apollo-server/[Apollo Server] package: -This section demonstrates using https://www.apollographql.com/docs/apollo-server/[Apollo Server] alongside Neo4jGraphQL to spin up a GraphQL API. +- `apollo-server` is the default package for Apollo Server, which we will pass our generated schema into. [source, bash] ---- -$ npm install @neo4j/graphql graphql neo4j-driver apollo-server +npm install @neo4j/graphql graphql neo4j-driver apollo-server ---- +== Step 3: Define your GraphQL type definitions + +The Neo4j GraphQL Library is primarily driven by type definitions which map to the nodes and relationships in your Neo4j database. In this example, we'll use a simple example of two node labels, "Actor" and "Movie". + +Open up the previously created `index.js` in your editor of choice and write out your type definitions. We'll also add all of our necessary package imports at this stage: + [source, javascript] ---- const { Neo4jGraphQL } = require("@neo4j/graphql"); +const { ApolloServer, gql } = require("apollo-server"); const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); -const typeDefs = ` +const typeDefs = gql` type Movie { title: String - year: Int - imdbRating: Float - genres: [Genre] @relationship(type: "IN_GENRE", direction: OUT) + actors: [Actor] @relationship(type: "ACTED_IN", direction: IN) } - type Genre { + type Actor { name: String - movies: [Movie] @relationship(type: "IN_GENRE", direction: IN) + movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT) } `; +---- +These type definitions are incredibly simple, defining the two previously described node labels, and a relationship "ACTED_IN" between the two. When generated, the schema will allow us to execute queries `actors` and `movies` to read data from the database. + +== Step 4: Create an instance of `Neo4jGraphQL` + +Now that we have our type definitions, we need to create an instance of the Neo4j GraphQL Library. To do this, we also need a Neo4j driver to connect to our database. For a database located at "bolt://localhost:7687", with a username of "neo4j" and a password of "password", add the following to the bottom of our `index.js` file: + +[source, javascript] +---- const driver = neo4j.driver( "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") + neo4j.auth.basic("neo4j", "password") ); const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); +---- + +== Step 5: Create an instance of `ApolloServer` + +The final section of code we need to add is to instantiate an Apollo Server instance using our generated schema, which will allow us to execute queries against it. + +Add the following to the bottom of `index.js`: +[source, javascript] +---- const server = new ApolloServer({ schema: neoSchema.schema, - context: ({ req }) => ({ req }), }); -server.listen(4000).then(() => console.log("Online")); +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`); +}); ---- +== Step 6: Start the server + +Finally, we're ready to start up our GraphQL server! Back in the command line, run the following command: + +[source, bash] +---- +node index.js +---- + +All going well, you should see the following output: + +[source, bash] +---- +🚀 Server ready at http://localhost:4000/ +---- + +Where http://localhost:4000/ is the default URL which Apollo Server starts at. + +== Step 7: Create your first nodes in the database + +We're now ready to add some data to our Neo4j database using our GraphQL API! + +Visit http://localhost:4000/ in your web browser and you'll see the following landing page: + +image::apollo-server-landing-page.png[title="Apollo Server Landing Page"] + +Click "Query your server" which will open the Sandbox. + +image::first-mutation.png[title="First Mutation"] + +At the moment our database is empty! So let's create a movie and an actor in that movie, all in one Mutation. The Mutation in the screenshot above can also be found below: + +[source, graphql] +---- +mutation { + createMovies( + input: [ + { + title: "Forrest Gump" + actors: { create: [{ node: { name: "Tom Hanks" } }] } + } + ] + ) { + movies { + title + actors { + name + } + } + } +} +---- + +Put this Mutation into the Operations panel and hit the blue "Run" button in the top right. When you execute the Mutation, you'll receive the following response, confirmation that the data has been created in the database! + +[source, json] +---- +{ + "data": { + "createMovies": { + "movies": [ + { + "title": "Forrest Gump", + "actors": [ + { + "name": "Tom Hanks" + } + ] + } + ] + } + } +} +---- + +We can now go back and query the data which we just added: + +image::first-query.png[title="First Query"] + +The query we in the screenshot above is querying for all movies and their actors in the database: + +[source, graphql] +---- +query { + movies { + title + actors { + name + } + } +} +---- + +We of course only have the one of each, so you will see the result below: + +[source, json] +---- +{ + "data": { + "movies": [ + { + "title": "Forrest Gump", + "actors": [ + { + "name": "Tom Hanks" + } + ] + } + ] + } +} +---- diff --git a/docs/images/apollo-server-landing-page.png b/docs/images/apollo-server-landing-page.png new file mode 100644 index 0000000000000000000000000000000000000000..f4af49d8837f61c68427a69c61ce86399c7d2ba7 GIT binary patch literal 89111 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 z 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 literal 0 HcmV?d00001 diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 77f3a5eae1..e4e2a76786 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -44,7 +44,7 @@ "@types/node": "14.0.27", "@types/pluralize": "0.0.29", "@types/randomstring": "1.1.6", - "apollo-server": "2.21.0", + "apollo-server": "^3.0.2", "dedent": "^0.7.0", "faker": "5.2.0", "graphql-tag": "2.11.0", diff --git a/yarn.lock b/yarn.lock index 5224e65470..2863bd5a21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,30 @@ __metadata: languageName: node linkType: hard +"@apollo/protobufjs@npm:1.2.2": + version: 1.2.2 + resolution: "@apollo/protobufjs@npm:1.2.2" + dependencies: + "@protobufjs/aspromise": ^1.1.2 + "@protobufjs/base64": ^1.1.2 + "@protobufjs/codegen": ^2.0.4 + "@protobufjs/eventemitter": ^1.1.0 + "@protobufjs/fetch": ^1.1.0 + "@protobufjs/float": ^1.0.2 + "@protobufjs/inquire": ^1.1.0 + "@protobufjs/path": ^1.1.2 + "@protobufjs/pool": ^1.1.0 + "@protobufjs/utf8": ^1.1.0 + "@types/long": ^4.0.0 + "@types/node": ^10.1.0 + long: ^4.0.0 + bin: + apollo-pbjs: bin/pbjs + apollo-pbts: bin/pbts + checksum: 75940f213d39728f2fdd662732eb6c085fb393bea01ccb58c5084a18559d1d7342bac3ebb44f93c9e86b8237c675ae81a04427866d156f93b16b691d020bfd44 + languageName: node + linkType: hard + "@apollo/protobufjs@npm:^1.0.3": version: 1.2.0 resolution: "@apollo/protobufjs@npm:1.2.0" @@ -77,6 +101,13 @@ __metadata: languageName: node linkType: hard +"@apollographql/apollo-tools@npm:^0.5.1": + version: 0.5.1 + resolution: "@apollographql/apollo-tools@npm:0.5.1" + checksum: f06b922b829167298ccfedd60bf9a918c537ceb83299a27d9e3369fe6009638cf58f3ed16ca3d88c94ec5b6e20867820c07f464d5666516d8d32a96fb82685c4 + languageName: node + linkType: hard + "@apollographql/graphql-playground-html@npm:1.6.26": version: 1.6.26 resolution: "@apollographql/graphql-playground-html@npm:1.6.26" @@ -95,6 +126,15 @@ __metadata: languageName: node linkType: hard +"@apollographql/graphql-playground-html@npm:1.6.29": + version: 1.6.29 + resolution: "@apollographql/graphql-playground-html@npm:1.6.29" + dependencies: + xss: ^1.0.8 + checksum: ea45ba54c1110f21f5bfc5751d29802346165568f47431ebcf9411164bb98ea551dab0fd5cc95bf688be98f0df5414811b04a0b794621782db9be20d0cf60939 + languageName: node + linkType: hard + "@apollographql/graphql-upload-8-fork@npm:^8.1.3": version: 8.1.3 resolution: "@apollographql/graphql-upload-8-fork@npm:8.1.3" @@ -599,6 +639,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/merge@npm:6.2.16": + version: 6.2.16 + resolution: "@graphql-tools/merge@npm:6.2.16" + dependencies: + "@graphql-tools/schema": ^8.0.1 + "@graphql-tools/utils": 8.0.1 + tslib: ~2.3.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: 38185615f046875e0110cfe23f551a8c05da8216b40693f5fbc442f047f29ff8d4f2d5f8f6b9846990ed32108a4765b3103791eab162a2c71872103a5481f964 + languageName: node + linkType: hard + "@graphql-tools/merge@npm:^6.2.13": version: 6.2.13 resolution: "@graphql-tools/merge@npm:6.2.13" @@ -612,6 +665,21 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/mock@npm:^8.1.2": + version: 8.1.6 + resolution: "@graphql-tools/mock@npm:8.1.6" + dependencies: + "@graphql-tools/merge": 6.2.16 + "@graphql-tools/schema": ^8.0.1 + "@graphql-tools/utils": 8.0.1 + fast-json-stable-stringify: ^2.1.0 + tslib: ~2.3.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: 5e64dc869f1488af4e739fe59b5489275f518a400e9b1eda461b7a79f212bd9ccb0dfb41b8e5ecb1d52a238d67f4d59a317f34730d77951a364e6b67155bb82f + languageName: node + linkType: hard + "@graphql-tools/schema@npm:^7.0.0, @graphql-tools/schema@npm:^7.1.3": version: 7.1.3 resolution: "@graphql-tools/schema@npm:7.1.3" @@ -624,6 +692,44 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/schema@npm:^7.1.5": + version: 7.1.5 + resolution: "@graphql-tools/schema@npm:7.1.5" + dependencies: + "@graphql-tools/utils": ^7.1.2 + tslib: ~2.2.0 + value-or-promise: 1.0.6 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: 23b1e5443919a2d9abff535bfa9cd935e3ee3f0b2e3938d396af40ffcbf8d90bacf74fd3faf895845b1b0bf7cada5f7e0d1fd67f667ec02752884f25b1e19ac4 + languageName: node + linkType: hard + +"@graphql-tools/schema@npm:^8.0.1": + version: 8.0.1 + resolution: "@graphql-tools/schema@npm:8.0.1" + dependencies: + "@graphql-tools/merge": 6.2.16 + "@graphql-tools/utils": 8.0.1 + tslib: ~2.3.0 + value-or-promise: 1.0.10 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: 852e1f1dc78854bc2648b5078d955c54590260ddea57e927daeb6740bf02e21fd0aac8147d854c6892304f06fead8b594fd19fbeb0ca28df9c1370960a98843d + languageName: node + linkType: hard + +"@graphql-tools/utils@npm:8.0.1": + version: 8.0.1 + resolution: "@graphql-tools/utils@npm:8.0.1" + dependencies: + tslib: ~2.3.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: d66fca40457f91aeb5daeed9824bab688fe34c05cb6b1f3e5a34394464822a33dbc468a2514caf3128063aaafeb0c869adb9e39a4ec82659675b71c487e8fe60 + languageName: node + linkType: hard + "@graphql-tools/utils@npm:^7.1.2, @graphql-tools/utils@npm:^7.7.0": version: 7.8.0 resolution: "@graphql-tools/utils@npm:7.8.0" @@ -637,7 +743,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^7.10.0": +"@graphql-tools/utils@npm:^7.10.0, @graphql-tools/utils@npm:^7.9.0": version: 7.10.0 resolution: "@graphql-tools/utils@npm:7.10.0" dependencies: @@ -951,7 +1057,7 @@ __metadata: "@types/node": 14.0.27 "@types/pluralize": 0.0.29 "@types/randomstring": 1.1.6 - apollo-server: 2.21.0 + apollo-server: ^3.0.2 camelcase: ^6.2.0 debug: ^4.3.1 dedent: ^0.7.0 @@ -1278,6 +1384,16 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:1.19.1": + version: 1.19.1 + resolution: "@types/body-parser@npm:1.19.1" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: abbebf715ffda345ee9ffc53f4fdad033b63cffcae1e22ffe58c6c8dc251f4879efd6d06714171c106ea89ad3175d9d70021fdc470bf1f0234eb334f3922770e + languageName: node + linkType: hard + "@types/classnames@npm:^2.2.10": version: 2.3.0 resolution: "@types/classnames@npm:2.3.0" @@ -1322,6 +1438,13 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:2.8.12": + version: 2.8.12 + resolution: "@types/cors@npm:2.8.12" + checksum: f3b62196df42c286282b9dbda26e24d2264211d932e7d9c2240f361e27ef693a5cb03790daeb3610fd958a0690f4d31d45ef7848b0df7a0dd2afc698a4f9df76 + languageName: node + linkType: hard + "@types/cors@npm:2.8.8": version: 2.8.8 resolution: "@types/cors@npm:2.8.8" @@ -1410,6 +1533,17 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:4.17.24": + version: 4.17.24 + resolution: "@types/express-serve-static-core@npm:4.17.24" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + checksum: 30fc9b62a7cc52fa320fbdf11510b20d91c1aa55d99d90ecb51708b9178aa670f2aaa94eed01f68a96a1a80b3575f1143c0cb23b0e69d60070d6d6d42e40b62a + languageName: node + linkType: hard + "@types/express@npm:*, @types/express@npm:4.17.11": version: 4.17.11 resolution: "@types/express@npm:4.17.11" @@ -1422,6 +1556,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:4.17.13": + version: 4.17.13 + resolution: "@types/express@npm:4.17.13" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.18 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: 9f17da703df21e3f1cee2fe1864b9fcac2ab07c37382b972a194a3a484b41c1fbe4022b6cfe546f0171fd2d93b324dd3839512494f4cba639c2afa021e6dbb12 + languageName: node + linkType: hard + "@types/express@npm:4.17.7": version: 4.17.7 resolution: "@types/express@npm:4.17.7" @@ -2836,6 +2982,16 @@ __metadata: languageName: node linkType: hard +"apollo-datasource@npm:^3.0.3": + version: 3.0.3 + resolution: "apollo-datasource@npm:3.0.3" + dependencies: + apollo-server-caching: ^3.0.1 + apollo-server-env: ^4.0.3 + checksum: fb3729cdc85169899c99444e109ae4b671be68752d4d1d02de8d8fe66577c958d6accf8502e129098745e157d2168a63d69513be2fa6bb1e4a13c199c11a2008 + languageName: node + linkType: hard + "apollo-env@npm:^0.6.6": version: 0.6.6 resolution: "apollo-env@npm:0.6.6" @@ -2872,6 +3028,19 @@ __metadata: languageName: node linkType: hard +"apollo-graphql@npm:^0.9.0": + version: 0.9.3 + resolution: "apollo-graphql@npm:0.9.3" + dependencies: + core-js-pure: ^3.10.2 + lodash.sortby: ^4.7.0 + sha.js: ^2.4.11 + peerDependencies: + graphql: ^14.2.1 || ^15.0.0 + checksum: 0da8498289a5a6f7b586d7a4336ec2219069c0136b70b3480079ac6cb357a94a53a1e6a411f0a18e1752f6644d5935e3627e67be9b88da5eb7d1731d8513e783 + languageName: node + linkType: hard + "apollo-link-context@npm:1.0.20": version: 1.0.20 resolution: "apollo-link-context@npm:1.0.20" @@ -2942,6 +3111,15 @@ __metadata: languageName: node linkType: hard +"apollo-reporting-protobuf@npm:^3.0.0": + version: 3.0.0 + resolution: "apollo-reporting-protobuf@npm:3.0.0" + dependencies: + "@apollo/protobufjs": 1.2.2 + checksum: fec8965cac06753090a7625e7c0f06db136e1680dd6c28a92d961222f3c3c65764d559eda44a1088c8bf50f168b757131e36960dfda265b82e282d6ff6e26dfd + languageName: node + linkType: hard + "apollo-server-caching@npm:^0.5.3": version: 0.5.3 resolution: "apollo-server-caching@npm:0.5.3" @@ -2960,7 +3138,16 @@ __metadata: languageName: node linkType: hard -"apollo-server-core@npm:^2.19.0, apollo-server-core@npm:^2.21.0, apollo-server-core@npm:^2.23.0": +"apollo-server-caching@npm:^3.0.1": + version: 3.0.1 + resolution: "apollo-server-caching@npm:3.0.1" + dependencies: + lru-cache: ^6.0.0 + checksum: 0fd19a71bf017ba31de9b9fa751f141407986cdaaad423cc265186bfb51e0f6cfef53e391172dd470b03fc68f5e302b059942bcf012b497aaf18337814c2c4b7 + languageName: node + linkType: hard + +"apollo-server-core@npm:^2.19.0, apollo-server-core@npm:^2.23.0": version: 2.23.0 resolution: "apollo-server-core@npm:2.23.0" dependencies: @@ -2996,6 +3183,37 @@ __metadata: languageName: node linkType: hard +"apollo-server-core@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-core@npm:3.1.1" + dependencies: + "@apollographql/apollo-tools": ^0.5.1 + "@apollographql/graphql-playground-html": 1.6.29 + "@graphql-tools/mock": ^8.1.2 + "@graphql-tools/schema": ^7.1.5 + "@graphql-tools/utils": ^7.9.0 + "@josephg/resolvable": ^1.0.0 + apollo-datasource: ^3.0.3 + apollo-graphql: ^0.9.0 + apollo-reporting-protobuf: ^3.0.0 + apollo-server-caching: ^3.0.1 + apollo-server-env: ^4.0.3 + apollo-server-errors: ^3.0.1 + apollo-server-plugin-base: ^3.1.1 + apollo-server-types: ^3.1.1 + async-retry: ^1.2.1 + fast-json-stable-stringify: ^2.1.0 + graphql-tag: ^2.11.0 + loglevel: ^1.6.8 + lru-cache: ^6.0.0 + sha.js: ^2.4.11 + uuid: ^8.0.0 + peerDependencies: + graphql: ^15.3.0 + checksum: 157faaa2a55a8cecf54830e447e7f10956dae99205c054a10a52ad56fca27012a63d97888480fe242368b52bad7475ce990edc73146ded01f28d273c950a88b6 + languageName: node + linkType: hard + "apollo-server-env@npm:^3.0.0": version: 3.0.0 resolution: "apollo-server-env@npm:3.0.0" @@ -3006,6 +3224,15 @@ __metadata: languageName: node linkType: hard +"apollo-server-env@npm:^4.0.3": + version: 4.0.3 + resolution: "apollo-server-env@npm:4.0.3" + dependencies: + node-fetch: ^2.6.1 + checksum: d4e0f626c54fe2850c9c3c5ce2723ff27dcd634aec51bf38c466271f42222dda02af5d26d8f39f2e6c55e4b71c46f50ae851bc5f8dee8e042e7671fa341fc1c2 + languageName: node + linkType: hard + "apollo-server-errors@npm:^2.5.0": version: 2.5.0 resolution: "apollo-server-errors@npm:2.5.0" @@ -3015,6 +3242,15 @@ __metadata: languageName: node linkType: hard +"apollo-server-errors@npm:^3.0.1": + version: 3.0.1 + resolution: "apollo-server-errors@npm:3.0.1" + peerDependencies: + graphql: ^15.3.0 + checksum: 7ffce7299ecaf3ad1f23ea5fbb580ec21fd3412730f6387270fd14a86755d58e909a107c07d334f219d894bce9ebd5d3354e99d827f4ab2d71d2db1a1d3b1e99 + languageName: node + linkType: hard + "apollo-server-express@npm:2.19.0": version: 2.19.0 resolution: "apollo-server-express@npm:2.19.0" @@ -3042,7 +3278,7 @@ __metadata: languageName: node linkType: hard -"apollo-server-express@npm:^2.21.0, apollo-server-express@npm:^2.23.0": +"apollo-server-express@npm:^2.23.0": version: 2.23.0 resolution: "apollo-server-express@npm:2.23.0" dependencies: @@ -3069,6 +3305,28 @@ __metadata: languageName: node linkType: hard +"apollo-server-express@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-express@npm:3.1.1" + dependencies: + "@types/accepts": ^1.3.5 + "@types/body-parser": 1.19.1 + "@types/cors": 2.8.12 + "@types/express": 4.17.13 + "@types/express-serve-static-core": 4.17.24 + accepts: ^1.3.5 + apollo-server-core: ^3.1.1 + apollo-server-types: ^3.1.1 + body-parser: ^1.19.0 + cors: ^2.8.5 + parseurl: ^1.3.3 + peerDependencies: + express: ^4.17.1 + graphql: ^15.3.0 + checksum: 25c627cfa88208b57a90153037e5489ac38b29df9f08fd0eec6b48943b7a121c22aa1b43c97bad7f3f2d3ad39adde63915834f040c6f637585226a153b30e5c3 + languageName: node + linkType: hard + "apollo-server-plugin-base@npm:^0.11.0": version: 0.11.0 resolution: "apollo-server-plugin-base@npm:0.11.0" @@ -3080,6 +3338,17 @@ __metadata: languageName: node linkType: hard +"apollo-server-plugin-base@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-plugin-base@npm:3.1.1" + dependencies: + apollo-server-types: ^3.1.1 + peerDependencies: + graphql: ^15.3.0 + checksum: 095cf37cc403aa8c413de79988f259d0b24eea0ef35a3b313873c8c632460e4cc3bd3aeed8d3058ff8be280887eb195c4af8915927b49c701c0b5e42ca3a7a15 + languageName: node + linkType: hard + "apollo-server-testing@npm:2.19.0": version: 2.19.0 resolution: "apollo-server-testing@npm:2.19.0" @@ -3117,19 +3386,16 @@ __metadata: languageName: node linkType: hard -"apollo-server@npm:2.21.0": - version: 2.21.0 - resolution: "apollo-server@npm:2.21.0" +"apollo-server-types@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-types@npm:3.1.1" dependencies: - apollo-server-core: ^2.21.0 - apollo-server-express: ^2.21.0 - express: ^4.0.0 - graphql-subscriptions: ^1.0.0 - graphql-tools: ^4.0.8 - stoppable: ^1.1.0 + apollo-reporting-protobuf: ^3.0.0 + apollo-server-caching: ^3.0.1 + apollo-server-env: ^4.0.3 peerDependencies: - graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 - checksum: ab5e80064c95a33ca9875256639197123542d1569a645537324403508a1e42d79f9bc464d1117d9f8bfc7e25e9045f417b198a0877172e71d56e62964535f417 + graphql: ^15.3.0 + checksum: 4257799fc3ef5b147bafe2e977ae0a352c9ddc37edbb2b1c669ac739ecf61ae82207753d5a73cc6b81ba04c364adfc2412a2ad5b5e27ad9ce64dc9111c206ef3 languageName: node linkType: hard @@ -3149,6 +3415,19 @@ __metadata: languageName: node linkType: hard +"apollo-server@npm:^3.0.2": + version: 3.1.1 + resolution: "apollo-server@npm:3.1.1" + dependencies: + apollo-server-core: ^3.1.1 + apollo-server-express: ^3.1.1 + express: ^4.17.1 + peerDependencies: + graphql: ^15.3.0 + checksum: 1d5a546a535087fc3cb5d3df8233212adf7077306fb90c14215b70422ba02512edf7358a73b3a2098f5786c80cbe445c9f767553c54eba81aeb6141a2db17e5f + languageName: node + linkType: hard + "apollo-tracing@npm:^0.13.0": version: 0.13.0 resolution: "apollo-tracing@npm:0.13.0" @@ -3631,7 +3910,7 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.19.0, body-parser@npm:^1.18.3": +"body-parser@npm:1.19.0, body-parser@npm:^1.18.3, body-parser@npm:^1.19.0": version: 1.19.0 resolution: "body-parser@npm:1.19.0" dependencies: @@ -4444,6 +4723,13 @@ __metadata: languageName: node linkType: hard +"core-js-pure@npm:^3.10.2": + version: 3.15.2 + resolution: "core-js-pure@npm:3.15.2" + checksum: 098de70a85d245422046c8859d699f8d1a5e971d12074f26e108a3db539430e641af4bb311888efbb944cfdc747d4b56ec8a1970458a2ce444cc834cabdf1772 + languageName: node + linkType: hard + "core-js@npm:^3.0.1": version: 3.11.0 resolution: "core-js@npm:3.11.0" @@ -6047,7 +6333,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 7df3fabfe445d65953b2d9d9d3958bd895438b215a40fb87dae8b2165c5169a897785eb5d51e6cf0eb03523af756e3d82ea01083f6ac6341fe16db532fee3016 @@ -10510,7 +10796,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"parseurl@npm:^1.3.2, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.2, parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 52c9e86cb58e38b28f1a50a6354d16648974ab7a2b91b209f97102840471de8adf524427774af6d5bc482fb7c0a6af6ba08ab37de9a1a7ae389ebe074015914b @@ -13285,6 +13571,13 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard +"tslib@npm:~2.3.0": + version: 2.3.0 + resolution: "tslib@npm:2.3.0" + checksum: 7b4fc9feff0f704743c3760f5d8d708f6417fac6458159e63df3a6b1100f0736e3b99edb9fe370f274ad15160a1f49ff05cb49402534c818ff552c48e38c3e6e + languageName: node + linkType: hard + "tsutils@npm:^3.17.1": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -13768,6 +14061,20 @@ typescript@4.1.3: languageName: node linkType: hard +"value-or-promise@npm:1.0.10": + version: 1.0.10 + resolution: "value-or-promise@npm:1.0.10" + checksum: 381d254e1bf6e4b56a1512591664c88989e2fc584ac939e3164ba92055f078862e1b7dd8d0bed33d7772e16ac7ff14d521bd06fa7fd6468305132cb8958daf4c + languageName: node + linkType: hard + +"value-or-promise@npm:1.0.6": + version: 1.0.6 + resolution: "value-or-promise@npm:1.0.6" + checksum: ea5fa311aad0c6f63feccb6891e162f847e9bb2b257813eef8fc945f9e48b3df5fe7402227309171f0b096b6c2d17e163d6384ec3de819f49743537bde078699 + languageName: node + linkType: hard + "vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" From 9ca32433e4698ae94fb07201f2a0d040c350c479 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 11:32:03 +0100 Subject: [PATCH 11/20] Add type definitions basics page --- docs/antora/content-nav.adoc | 1 + docs/asciidoc/type-definitions/basics.adoc | 61 ++++++++++++++++++++++ docs/asciidoc/type-definitions/index.adoc | 40 +++----------- docs/docbook/content-map.xml | 3 ++ 4 files changed, 73 insertions(+), 32 deletions(-) create mode 100644 docs/asciidoc/type-definitions/basics.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 6420239f39..36dc8cfa73 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -3,6 +3,7 @@ ** xref:getting-started/index.adoc[] ** xref:type-definitions/index.adoc[] *** xref:type-definitions/types/index.adoc[] +*** xref:type-definitions/basics/index.adoc[] *** xref:type-definitions/unions-and-interfaces/index.adoc[] *** xref:type-definitions/relationships/index.adoc[] *** xref:type-definitions/access-control/index.adoc[] diff --git a/docs/asciidoc/type-definitions/basics.adoc b/docs/asciidoc/type-definitions/basics.adoc new file mode 100644 index 0000000000..38eee04413 --- /dev/null +++ b/docs/asciidoc/type-definitions/basics.adoc @@ -0,0 +1,61 @@ +[[type-definitions-basics]] += Basics + +Each type in your GraphQL type definitions can be mapped to an entity in your Neo4j database. + +== Nodes + +The most basic mapping is of GraphQL types to Neo4j nodes, where the GraphQL type name maps to the Neo4j node label. + +For example, to represent a node with label "Movie" and a single property "title" of type string: + +[source, graphql] +---- +type Movie { + title: String +} +---- + +== Relationships + +Relationships are represented by marking particular fields with a directive. This directive, `@relationship`, defines the relationship type in the database, as well as which direction that relationship goes in. + +Let's add a second node type, "Actor", and connect the two together: + +[source, graphql] +---- +type Movie { + title: String + actors: [Actor] @relationship(type: "ACTED_IN", direction: IN) +} + +type Actor { + name: String + movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT) +} +---- + +Note there is a directive on each "end" of the relationship in this case, but it is not essential. + +=== Relationship properties + +In order to add relationship properties to a relationship, we need to add a new type to our type definitions, but this time it will be of type `interface`. For example, for our "ACTED_IN" relationship, let's add a property "roles": + +[source, graphql] +---- +type Movie { + title: String + actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") +} + +type Actor { + name: String + movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") +} + +interface ActedIn { + roles: [String] +} +---- + +Note that in addition to this new interface type, we have added a key `properties` to the existing `@relationship` directives. diff --git a/docs/asciidoc/type-definitions/index.adoc b/docs/asciidoc/type-definitions/index.adoc index 1af466c8dc..ed02870ebe 100644 --- a/docs/asciidoc/type-definitions/index.adoc +++ b/docs/asciidoc/type-definitions/index.adoc @@ -1,38 +1,14 @@ [[type-definitions]] = Type Definitions -* <> -* <> -* <> -* <> -* <> -* <> -* <> +- <> - Learn how to define your nodes and relationships using GraphQL type definitions. +- <> - Learn about the various data types available in the Neo4j GraphQL Library. +- <> - Learn about two GraphQL concepts, unions and interfaces, and how they map to the Neo4j database. +- <> - Learn more about defining relationships using the Neo4j GraphQL Library. +- <> - Learn about how to restrict access to certain types or fields. +- <> - Learn about certain types which you can enable autogeneration of values for. +- <> - Learn about how to add custom Cypher to your type definitions. +- <> - Learn about different ways of setting default values for particular fields. -== Basics - -=== Nodes - -To represent a node in the GraphQL schema use the type definition; - -[source, graphql] ----- -type Node { - id: ID -} ----- - - -=== Relationships - -To represent a relationship between two nodes use the `@relationship` directive; - -[source, graphql] ----- -type Node { - id: ID - related: [Node] @relationship(type: "RELATED", direction: OUT) -} ----- diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 6248463672..8b570f2631 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -13,6 +13,9 @@ + + + From 27b4f960769e9c68f5b3e0bd215494deb745d952 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 11:39:06 +0100 Subject: [PATCH 12/20] Add documentation for all GraphQL types --- docs/asciidoc/type-definitions/types.adoc | 63 ++++++++++++++++------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/docs/asciidoc/type-definitions/types.adoc b/docs/asciidoc/type-definitions/types.adoc index 6764e600b3..35fe2000eb 100644 --- a/docs/asciidoc/type-definitions/types.adoc +++ b/docs/asciidoc/type-definitions/types.adoc @@ -1,32 +1,31 @@ [[type-definitions-types]] = Types -Neo4j GraphQL supports all of the default GraphQL https://graphql.org/learn/schema/#scalar-types[scalar types] as well as the additional scalar and object types specified in this document. +Neo4j GraphQL supports all of the default GraphQL https://graphql.org/learn/schema/#scalar-types[scalar types] as well as additional scalar and object types specific to the Neo4j database. -[[type-definitions-types-datetime]] -== `DateTime` -ISO datetime string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-datetime[datetime] temporal type. +== Int -[source, graphql] ----- -type User { - createdAt: DateTime -} ----- +One of the default GraphQL scalar types. Supports up to 53-bit values - see <> for 64-bit value support. -[[type-definitions-types-date]] -== `Date` -"yyyy-mm-dd" date string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-date[date] temporal type. +== Float -[source, graphql] ----- -type Movie { - releaseDate: Date -} ----- +One of the default GraphQL scalar types. + +== String + +One of the default GraphQL scalar types. + +== Boolean + +One of the default GraphQL scalar types. + +== ID + +One of the default GraphQL scalar types. Stored as a string in the database and always returned as a string. [[type-definitions-types-bigint]] == `BigInt` + Supports up to 64 bit integers, serialized as strings in variables and in data responses. Shares the same <> as the other numeric types. [source, graphql] @@ -47,6 +46,32 @@ query { } ---- +== Temporal Types + +[[type-definitions-types-datetime]] +=== `DateTime` + +ISO datetime string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-datetime[datetime] temporal type. + +[source, graphql] +---- +type User { + createdAt: DateTime +} +---- + +[[type-definitions-types-date]] +=== `Date` + +"yyyy-mm-dd" date string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-date[date] temporal type. + +[source, graphql] +---- +type Movie { + releaseDate: Date +} +---- + [[type-definitions-types-spatial-types]] == Spatial Types From 063abab3b888fe4b4245defd078b994dc6f4028c Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 11:39:32 +0100 Subject: [PATCH 13/20] Fix order of type definitions pages --- docs/antora/content-nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 36dc8cfa73..e135c1ffbd 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -2,8 +2,8 @@ ** xref:introduction/index.adoc[] ** xref:getting-started/index.adoc[] ** xref:type-definitions/index.adoc[] -*** xref:type-definitions/types/index.adoc[] *** xref:type-definitions/basics/index.adoc[] +*** xref:type-definitions/types/index.adoc[] *** xref:type-definitions/unions-and-interfaces/index.adoc[] *** xref:type-definitions/relationships/index.adoc[] *** xref:type-definitions/access-control/index.adoc[] From 30efd3e14a811a2e4f225cb02f94dbaf7d4291bf Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 14:13:45 +0100 Subject: [PATCH 14/20] Rewrite introduction page with features, interacting and deployment section --- docs/asciidoc/introduction.adoc | 76 +++++++++++++++++----- docs/asciidoc/type-definitions/cypher.adoc | 2 +- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/docs/asciidoc/introduction.adoc b/docs/asciidoc/introduction.adoc index a92d5f2b69..cfe4cc4036 100644 --- a/docs/asciidoc/introduction.adoc +++ b/docs/asciidoc/introduction.adoc @@ -1,39 +1,79 @@ [[introduction]] = Introduction -In this section you will find; notes on background information, links and resources, dependencies and requirements, plus pointers on where to get support. If you are already familiar with Neo4j GraphQL, or just want to get started, then jump directly to <>. +The Neo4j GraphQL Library is a highly flexible, low-code, open source JavaScript library that enables rapid API development for cross-platform and mobile applications by tapping into the power of connected data. -== Why Neo4j and GraphQL? +With Neo4j as the graph database, the GraphQL Library makes it simple for applications to have application data treated as a graph natively from the front-end all the way to storage, avoiding duplicate schema work and ensuring flawless integration between front-end and backend developers. -Bringing the native graph storage of Neo4j to the GraphQL ecosystem has been a long time in the making. Translating your GraphQL queries to Cypher that the Neo4j database can understand means that we can easily eliminate the n+1 issue that plagues a lot of GraphQL implementations. +Written in TypeScript, the library's schema-first paradigm lets developers focus on the application data they need, while taking care of the heavy lifting involved in building the API. -== What is Neo4j GraphQL ? +> Just want to get moving with the Neo4j GraphQL Library? Check out the <> guide! -It is a GraphQL to Cypher query execution layer for Neo4j and **JavaScript** GraphQL implementations. Such an implementation makes it easier for developers to use Neo4j and GraphQL together. +== How does it work? -Taking a set of Type Definitions; we produce an executable GraphQL Schema, where a given GraphQL query is transformed into a single Cypher query. This translation means users can let the library handle all the database communications, and thus enabling users to focus on building great applications. +By supplying the Neo4j GraphQL Library with a set of type definitions describing the shape of your graph data, it can generate an entire executable schema with all of the additional types needed to execute queries and mutations to interact with your Neo4j database. -Our GraphQL implementation exposes two products; +For every query and mutation that is executed against this generated schema, the Neo4j GraphQL Library generates a single Cypher query which is executed against the database. This eliminates the infamous https://www.google.com/search?q=graphql+n%2B1[N+1 Problem] which can make GraphQL implementations slow and inefficient. -1. `Neo4jGraphQL` - Used for GraphQL API's such as Apollo Server -2. <> - A Handy tool, to use in application code, driven by GraphQL Type Definitions. +== Features -With a powerful feature set including; +The Neo4j GraphQL Library presents a large feature set for interacting with a Neo4j database using GraphQL: -1. <> -2. Nested Mutations +- Automatic generation of <> and <> for CRUD interactions +- Various <>, including temporal and spatial types +- Support for both node and relationship properties +- Extensibility through the <> and/or <> +- Extensive <> and <> options +- Options for value <> and <> +- Multiple <> options +- Comprehensive authentication and authorisation options (<>), and additional <> options +- An <> (Object Graph Mapper) for programmatic interaction with your GraphQL API -Overall anyone in the Javascript ecosystem, wanting to use either Neo4j and or GraphQL should find Neo4j GraphQL an indispensable tool for building great applications. +== Interaction + +In our <> guide, we start up a server using Apollo Server. This bundles Apollo Sandbox which can be used to interact directly with your GraphQL API with no front-end. + +There are a variety of front-end frameworks with clients for interacting with GraphQL APIs: + +- https://reactjs.org/[React] - support through https://www.apollographql.com/docs/react/[Apollo Client] +- https://vuejs.org/[Vue.js] - support through https://apollo.vuejs.org/[Vue Apollo] +- https://angularjs.org/[AngularJS] - support through https://apollo-angular.com/docs/[Apollo Angular] + +== Deployment + +There are a variety of methods for deploying GraphQL APIs, which we will not go into the details of in this documentation. + +However, Apollo has documented a subset in their https://www.apollographql.com/docs/apollo-server/deployment[Deployment] documentation, which will be a good starting point. + +== Versioning + +The Neo4j GraphQL Library uses https://semver.org/[Semantic Versioning]. Given a version number `MAJOR.MINOR.PATCH`, the increment is based on: + +- `MAJOR` - incompatible API changes compared to the previous `MAJOR` version, for which you will likely have to migrate. +- `MINOR` - new features have been added in a backwards compatible manner. +- `PATCH` - bug fixes have been added in a backwards compatible manner. + +Additionally, prerelease version numbers may have additional suffixes, for example `MAJOR.MINOR.PATCH-PRERELEASE.NUMBER`, where `PRERELEASE` is one of the following: + +- `alpha` - unstable prerelease artifacts, and the API may change between releases during this phase. +- `beta` - feature complete prerelease artifacts, which will be more stable than `alpha` releases but will likely still contain bugs. +- `rc` - release candidate release artifacts where each could be promoted to a stable release, in a last effort to find trailing bugs. + +`NUMBER` in the suffix is simply an incrementing release number in each phase. == Requirements -1. https://neo4j.com/[Neo4j Database] 4.1.0 and above -2. https://neo4j.com/developer/neo4j-apoc/[APOC] 4.1.0 and above + +1. https://neo4j.com/[Neo4j Database] 4.1.0+ +2. https://neo4j.com/developer/neo4j-apoc/[APOC] 4.1.0+ +3. https://nodejs.org/en/[Node.js] 12+ == Resources -1. https://github.com/neo4j/graphql[Github] -2. https://github.com/neo4j/graphql/issues[Bug Tracker] -3. https://www.npmjs.com/package/@neo4j/graphql[NPM] + +1. https://github.com/neo4j/graphql[GitHub] +2. https://github.com/neo4j/graphql/issues[Issue Tracker] +3. https://www.npmjs.com/package/@neo4j/graphql[npm package] == Licence + 1. Documentation: link:{common-license-page-uri}[Creative Commons 4.0] 2. Source: https://www.apache.org/licenses/LICENSE-2.0[Apache 2.0] diff --git a/docs/asciidoc/type-definitions/cypher.adoc b/docs/asciidoc/type-definitions/cypher.adoc index 90d570624d..8b4be795c1 100644 --- a/docs/asciidoc/type-definitions/cypher.adoc +++ b/docs/asciidoc/type-definitions/cypher.adoc @@ -1,5 +1,5 @@ [[type-definitions-cypher]] -= `@cypher` Directive += `@cypher` directive The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. From c17792cf43d06e9714dab1c7dfd74cffc53b2578 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 14:14:03 +0100 Subject: [PATCH 15/20] Change documentation for interfaces, highlighting that they are used for relationship properties --- docs/asciidoc/type-definitions/unions-and-interfaces.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc index 16a843783b..5b2ab9a94c 100644 --- a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc +++ b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc @@ -156,4 +156,6 @@ mutation CreateUserAndContent { == Interface Types -Using interface types will give you no database support, therefore there is no support for querying, updating, deleting, filtering. But instead used as a language feature to safeguard your schema. Great for when dealing with repetitive or large schemas you can essentially put "The side railings up". +At the moment, the only support that the Neo4j GraphQL Library offers for interfaces is in the definition of relationship properties. + +Beyond this, feel free to use them in your implementation of node types, however the library will offer no database support for these - it will essentially ignore them and focus only on the implementing GraphQL object types. From dc7910c89fb7ae79f1210420ef4b08b9888c6298 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 14:14:50 +0100 Subject: [PATCH 16/20] Fix references to pagination --- docs/asciidoc/ogm/methods/find.adoc | 2 +- docs/asciidoc/queries.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/asciidoc/ogm/methods/find.adoc b/docs/asciidoc/ogm/methods/find.adoc index 95ffe313a5..5647dc969a 100644 --- a/docs/asciidoc/ogm/methods/find.adoc +++ b/docs/asciidoc/ogm/methods/find.adoc @@ -40,7 +40,7 @@ const users = await User.find({ JavaScript object representation of the GraphQL `where` input type, used for <>. === `options` -JavaScript object representation of the GraphQL `options` input type, used for <>. +JavaScript object representation of the GraphQL `options` input type, used for <>. === `selectionSet` diff --git a/docs/asciidoc/queries.adoc b/docs/asciidoc/queries.adoc index 148141707c..c3196ece1a 100644 --- a/docs/asciidoc/queries.adoc +++ b/docs/asciidoc/queries.adoc @@ -4,7 +4,7 @@ Each object defined in type definitions will have a Query field generated for it. Each Query field accepts two arguments: * `where`: Used to specify the <> which should be applied whilst querying the data -* `options`: Used to specify the options for <> and <> +* `options`: Used to specify the options for <> and <> == Example From 939c73ea94aa0b6a57afff1efe502f7529308437 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 15:55:45 +0100 Subject: [PATCH 17/20] Continued rewriting and restructuring --- docs/antora/content-nav.adoc | 6 +- docs/asciidoc/custom-resolvers.adoc | 30 +-- docs/asciidoc/driver-and-config.adoc | 93 --------- docs/asciidoc/driver-configuration.adoc | 187 ++++++++++++++++++ docs/asciidoc/mutations/index.adoc | 8 +- .../ogm/examples/custom-resolvers.adoc | 97 +++++++++ docs/asciidoc/ogm/examples/index.adoc | 7 + docs/asciidoc/ogm/examples/rest-api.adoc | 85 ++++++++ docs/asciidoc/ogm/getting-started.adoc | 66 ------- docs/asciidoc/ogm/index.adoc | 31 +-- docs/asciidoc/ogm/installation.adoc | 22 +++ docs/asciidoc/ogm/selection-set.adoc | 69 ++++--- docs/asciidoc/pagination/cursor-based.adoc | 87 +++++++- docs/asciidoc/pagination/offset-based.adoc | 12 +- docs/asciidoc/queries.adoc | 49 +++-- docs/asciidoc/troubleshooting/faqs.adoc | 16 ++ .../index.adoc} | 14 +- docs/docbook/content-map.xml | 16 +- 18 files changed, 628 insertions(+), 267 deletions(-) delete mode 100644 docs/asciidoc/driver-and-config.adoc create mode 100644 docs/asciidoc/driver-configuration.adoc create mode 100644 docs/asciidoc/ogm/examples/custom-resolvers.adoc create mode 100644 docs/asciidoc/ogm/examples/index.adoc create mode 100644 docs/asciidoc/ogm/examples/rest-api.adoc delete mode 100644 docs/asciidoc/ogm/getting-started.adoc create mode 100644 docs/asciidoc/ogm/installation.adoc create mode 100644 docs/asciidoc/troubleshooting/faqs.adoc rename docs/asciidoc/{troubleshooting.adoc => troubleshooting/index.adoc} (76%) diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index e135c1ffbd..95455112bc 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -32,7 +32,10 @@ ** xref:directives/index.adoc[] ** xref:api-reference/index.adoc[] ** xref:ogm/index.adoc[] -*** xref:ogm/getting-started/index.adoc[] +*** xref:ogm/installation/index.adoc[] +*** xref:ogm/examples/index.adoc[] +**** xref:ogm/examples/custom-resolvers/index.adoc[] +**** xref:ogm/examples/rest-api/index.adoc[] *** xref:ogm/methods/index.adoc[] **** xref:ogm/methods/create/index.adoc[] **** xref:ogm/methods/find/index.adoc[] @@ -54,3 +57,4 @@ **** xref:guides/v2-migration/unions/index.adoc[] **** xref:guides/v2-migration/miscellaneous/index.adoc[] ** xref:troubleshooting/index.adoc[] +*** xref:troubleshooting/faqs/index.adoc[] diff --git a/docs/asciidoc/custom-resolvers.adoc b/docs/asciidoc/custom-resolvers.adoc index 6c48357a1e..5e22618747 100644 --- a/docs/asciidoc/custom-resolvers.adoc +++ b/docs/asciidoc/custom-resolvers.adoc @@ -23,8 +23,8 @@ const typeDefs = ` const resolvers = { User: { - fullName(obj) { - return `${obj.firstName} ${obj.lastName}`; + fullName(source) { + return `${source.firstName} ${source.lastName}`; }, }, }; @@ -37,28 +37,4 @@ const neoSchema = new Neo4jGraphQL({ == Custom Query/Mutation type field resolver -You can define additional, custom Queries and Mutations in your type definitions and provide custom resolvers for them. A prime use case for this is using the <> to manipulate types and fields which are not available through the API. - -[source, javascript] ----- -const typeDefs = ` - type User { - userId: ID! - } - - type Query { - users: [User] - } -`; - -const resolvers = { - Query: { - users: () => // implement resolver here - } -}; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - resolvers, -}); ----- +You can define additional custom Query and Mutation fields in your type definitions and provide custom resolvers for them. A prime use case for this is using the <> to manipulate types and fields which are not available through the API. You can find an example of it being used in this capacity in the <> example. diff --git a/docs/asciidoc/driver-and-config.adoc b/docs/asciidoc/driver-and-config.adoc deleted file mode 100644 index b3bfba7f07..0000000000 --- a/docs/asciidoc/driver-and-config.adoc +++ /dev/null @@ -1,93 +0,0 @@ -[[driver-configuration]] -= Driver Configuration - - -== Neo4j Driver -The https://github.com/neo4j/neo4j-javascript-driver[Neo4j javascript driver] must be present in either the context or construction of your `Neo4jGraphQL` API or at the construction of your `OGM`. - -=== `Neo4jGraphQL` -[source, javascript] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") -); - -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -const server = new ApolloServer({ - schema: neoSchema.schema, - context: ({ req }) => ({ req }), -}); ----- - -Or you can specify the driver at runtime using the context; - -[source, javascript] ----- -const server = new ApolloServer({ - schema: neoSchema.schema, - context: ({ req }) => ({ req, context }), -}); ----- - -=== `OGM` - -[source, javascript] ----- -const express = require("express"); -const { OGM } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") -); - -const ogm = new OGM({ typeDefs, driver }); ----- - -[[driver-configuration-database-compatibility]] -== Database Compatibility -Use the `checkNeo4jCompat` method available on either `Neo4jGraphQL` or the `OGM` to ensure the specified DBMS has the required; versions, functions and procedures. - -==== `Neo4jGraphQL` - -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -await neoSchema.checkNeo4jCompat(); ----- - -==== `OGM` - -[source, javascript] ----- -const ogm = new OGM({ typeDefs, driver }); -await ogm.checkNeo4jCompat(); ----- - -== Specifying Neo4j Database -The Neo4j database may be added to the GraphQL context object; - -[source, javascript] ----- -const server = new ApolloServer({ - schema, - context: { driver, driverConfig: { database: "sanmateo" } } -}); ----- - -== Specifying Neo4j Bookmarks -A Neo4j driver bookmark may be added to the GraphQL context object; - -[source, javascript] ----- -const server = new ApolloServer({ - schema, - context: { driver, driverConfig: { bookmarks: ["my-bookmark"] } } -}); ----- diff --git a/docs/asciidoc/driver-configuration.adoc b/docs/asciidoc/driver-configuration.adoc new file mode 100644 index 0000000000..1d22da0ac8 --- /dev/null +++ b/docs/asciidoc/driver-configuration.adoc @@ -0,0 +1,187 @@ +[[driver-configuration]] += Driver Configuration + +== Neo4j Driver +An instance of the https://github.com/neo4j/neo4j-javascript-driver[Neo4j JavaScript driver] must be present in either the GraphQL request context, or construction of your `Neo4jGraphQL` instance (or alternatively, `OGM`). + +The examples in this chapter assume a Neo4j database running at "bolt://localhost:7687" with a username of "neo4j" and a password of "password". + +=== Neo4j GraphQL Library + +==== Driver in `Neo4jGraphQL` constructor + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); + +const server = new ApolloServer({ + schema: neoSchema.schema, + context: ({ req }) => ({ req }), +}); +---- + +==== Driver in context + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); + +const server = new ApolloServer({ + schema: neoSchema.schema, + context: ({ req }) => ({ req, driver }), +}); +---- + +=== OGM + +[source, javascript] +---- +const { OGM } = require("@neo4j/graphql-ogm"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const ogm = new OGM({ typeDefs, driver }); +---- + +[[driver-configuration-database-compatibility]] +== Database Compatibility + +Use the `checkNeo4jCompat` method available on either a `Neo4jGraphQL` or `OGM` instance to ensure the specified DBMS is of the required version, and has the necessary functions and procedures available. The `checkNeo4jCompat` will throw an `Error` if the DBMS is incompatible, with details of the incompatibilities. + +=== `Neo4jGraphQL` + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); +await neoSchema.checkNeo4jCompat(); +---- + +=== `OGM` + +[source, javascript] +---- +const { OGM } = require("@neo4j/graphql-ogm"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const ogm = new OGM({ typeDefs, driver }); +await ogm.checkNeo4jCompat(); +---- + +== Specifying Neo4j database + +The Neo4j database may be added to the GraphQL context object: + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); + +const server = new ApolloServer({ + schema, + context: { driverConfig: { database: "my-database" } } +}); +---- + +== Specifying Neo4j Bookmarks + +A Neo4j driver bookmark may be added to the GraphQL context object: + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); + +const server = new ApolloServer({ + schema, + context: { driverConfig: { bookmarks: ["my-bookmark"] } } +}); +---- diff --git a/docs/asciidoc/mutations/index.adoc b/docs/asciidoc/mutations/index.adoc index 16729c9b67..5b03e916c6 100644 --- a/docs/asciidoc/mutations/index.adoc +++ b/docs/asciidoc/mutations/index.adoc @@ -3,13 +3,13 @@ Several Mutations are automatically generated for each type defined in type definitions, which are covered in the following chapters: -- <> -- <> -- <> +- <> - create nodes, and recursively create or connect further nodes in the graph +- <> - update nodes, and recursively perform any operations from there +- <> - delete nodes, and recursively delete or disconnect further nodes in the graph *A note on nested Mutations* -> You will see some basic examples of nested Mutations below, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! +> You will see some basic examples of nested Mutations in this chapter, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! > > However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. > diff --git a/docs/asciidoc/ogm/examples/custom-resolvers.adoc b/docs/asciidoc/ogm/examples/custom-resolvers.adoc new file mode 100644 index 0000000000..240d952d5a --- /dev/null +++ b/docs/asciidoc/ogm/examples/custom-resolvers.adoc @@ -0,0 +1,97 @@ +[[ogm-examples-custom-resolvers]] += Custom Resolvers + +A common case for using the OGM will be within custom resolvers inside a Neo4j GraphQL instance (very meta!), due to the fact that it has access to some fields which the Neo4j GraphQL Library may not. A common use case might be to have a `password` field marked with directive `@private`, and a custom resolver for creating users with passwords. + +To get started with this example, create your example application directory, create a new project and also the file which will contain our application code: + +[source, bash] +---- +mkdir ogm-custom-resolvers-example +cd ogm-custom-resolvers-example +npm init --yes +touch index.js +---- + +Then we need to install our dependencies: + +[source, bash] +---- +npm install @neo4j/graphql-ogm graphql neo4j-driver apollo-server +---- + +Assuming a running Neo4j database at "bolt://localhost:7687" with username "neo4j" and password "password", in our empty `index.js` file, add the following code: + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const { OGM } = require("@neo4j/graphql-ogm"); +const { ApolloServer } = require("apollo-server"); +const neo4j = require("neo4j-driver"); + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const typeDefs = ` + type User { + id: ID @id + username: String! + password: String! @private + } + + type Mutation { + createUser(username: String!, password: String!): User! + } +`; + +const ogm = new OGM({ typeDefs, driver }); +const User = ogm.model("User"); + +const resolvers = { + Mutation: { + createUser(_source, { username, password }): async () => { + const { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + + return users[0]; + }, + }, +}; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + resolvers, +}); + +const server = new ApolloServer({ + schema: neoSchema.schema, +}); + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`); +}); +---- + +Back in the command line, run the following command to start your server: + +[source, bash] +---- +node index.js +---- + +You should see the following output: + +[source, bash] +---- +🚀 Server ready at http://localhost:4000/ +---- + +You can execute the `createUser` Mutation against this GraphQL API, but when you go to query the user through the same API, the password field will not be available. diff --git a/docs/asciidoc/ogm/examples/index.adoc b/docs/asciidoc/ogm/examples/index.adoc new file mode 100644 index 0000000000..0f6c42c589 --- /dev/null +++ b/docs/asciidoc/ogm/examples/index.adoc @@ -0,0 +1,7 @@ +[[ogm-examples]] += Examples + +This chapter runs through some examples of how you might take advantage of the OGM. + +- <> - using the OGM in custom resolvers within your Neo4j GraphQL API +- <> - exposing your Neo4j GraphQL API through a REST API, using the OGM diff --git a/docs/asciidoc/ogm/examples/rest-api.adoc b/docs/asciidoc/ogm/examples/rest-api.adoc new file mode 100644 index 0000000000..1eaf426705 --- /dev/null +++ b/docs/asciidoc/ogm/examples/rest-api.adoc @@ -0,0 +1,85 @@ +[[ogm-examples-rest-api]] += REST API + +This example demonstrates how you might use the OGM without exposing a Neo4j GraphQL API endpoint. The example starts an https://expressjs.com/[Express] server and uses the OGM to interact with the Neo4j GraphQL Library, exposed over a REST endpoint. + +First, create your example application directory, create a new project and also the file which will contain our application code: + +[source, bash] +---- +mkdir ogm-rest-example +cd ogm-rest-example +npm init --yes +touch index.js +---- + +Then we need to install our dependencies: + +[source, bash] +---- +npm install @neo4j/graphql-ogm graphql neo4j-driver express +---- + +Assuming a running Neo4j database at "bolt://localhost:7687" with username "neo4j" and password "password", in our empty `index.js` file, add the following code: + +[source, javascript] +---- +const express = require("express"); +const { OGM } = require("@neo4j/graphql-ogm"); +const neo4j = require("neo4j-driver"); + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const typeDefs = ` + type User { + id: ID + name: String + } +`; + +const ogm = new OGM({ typeDefs, driver }); +const User = ogm.model("User"); + +const app = express(); + +app.get("/users", async (req, res) => { + const { search, offset, limit, sort } = req.query; + + const regex = search ? `(?i).*${search}.*` : null; + + const users = await User.find({ + where: { name_REGEX: regex }, + options: { + offset, + limit, + sort + } + }); + + return res.json(users).end(); +}); + +const port = 4000; +app.listen(port, () => { + console.log("Example app listening at http://localhost:${port}") +}); +---- + +In our application directory, you can run this application: + +[source, bash] +---- +node index.js +---- + +Which should output: + +[source, bash] +---- +Example app listening at http://localhost:4000 +---- + +The REST API should now be ready to accept requests at the URL logged. diff --git a/docs/asciidoc/ogm/getting-started.adoc b/docs/asciidoc/ogm/getting-started.adoc deleted file mode 100644 index 8603065a6c..0000000000 --- a/docs/asciidoc/ogm/getting-started.adoc +++ /dev/null @@ -1,66 +0,0 @@ -[[ogm-getting-started]] -= Getting Started - -This section will help you get started with Neo4j GraphQL OGM. Before starting we recommend readers have an understanding of; - -* `Neo4jGraphQL` <> -* <> -* <> - -== Installation -[source, bash] ----- -npm install @neo4j/graphql-ogm ----- - -graphql and neo4j-driver are are peerDependencies. - -== Requiring -[source, javascript] ----- -const { OGM } = require("@neo4j/graphql-ogm"); ----- - -== REST API Quick Start -This section demonstrates how to use the OGM outside of a GraphQL API. The example exposes a https://expressjs.com/[Express] server and uses the OGM, in the endpoint, to interact with Neo4j. - -[source, javascript] ----- -const express = require("express"); -const { OGM } = require("@neo4j/graphql-ogm"); -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") -); - -const typeDefs = ` - type User { - id: ID - name: String - } -`; - -const ogm = new OGM({ typeDefs, driver }); -const User = ogm.model("User"); - -const app = express(); -app.get("/users", async (req, res) => { - const { search, offset, limit, sort } = req.query; - - const regex = search ? `(?i).*${search}.*` : null; - - const users = await User.find({ - where: { name_REGEX: regex }, - options: { - offset, - limit, - sort - } - }); - - return res.json(users).end(); -}); -app.listen(4000, () => console.log("started")); ----- diff --git a/docs/asciidoc/ogm/index.adoc b/docs/asciidoc/ogm/index.adoc index 45e200770d..e63ea42d1a 100644 --- a/docs/asciidoc/ogm/index.adoc +++ b/docs/asciidoc/ogm/index.adoc @@ -1,22 +1,27 @@ [[ogm]] = OGM -Common applications won't just expose a single API. On the same instance as the GraphQL API there may be; scheduled jobs, authentication, migrations and not to forget any custom logic in the resolvers themselves. We expose an OGM (Object Graph Mapper) on top of the pre-existing GraphQL work and abstractions. +Most applications won't just expose a single GraphQL API. There may also be scheduled jobs, authentication and migrations keeping an application ticking over. We provide an OGM (Object Graph Mapper) which can be used to programmatically interact with your Neo4j GraphQL API, which may help with achieving these goals. -* <> -* <> -* <> -* <> -* <> +- <> +- <> +- <> +- <> +- <> +- <> +> Before diving into the OGM, it's important to have a good understanding of the Neo4h GraphQL Library first. It's recommended to at least work through the <> guide. -== Directives -The following directives are excluded from the OGM's schema; +== Excluded directives -* `@auth` -* `@exclude` -* `@private` -* `@readonly` -* `@writeonly` +The following directives are excluded from the OGM's schema: + +- `@auth` +- `@exclude` +- `@private` +- `@readonly` +- `@writeonly` + +This is because the OGM is only ever used programmatically, as opposed to an exposed API which needs these security measures. See also: <> diff --git a/docs/asciidoc/ogm/installation.adoc b/docs/asciidoc/ogm/installation.adoc new file mode 100644 index 0000000000..5ff063f873 --- /dev/null +++ b/docs/asciidoc/ogm/installation.adoc @@ -0,0 +1,22 @@ +[[ogm-installation]] += Installation + +The OGM is very easy to install into a new or existing Node.js project. However it does have a couple of dependencies. The OGM depends on the Neo4j GraphQL Library, which will be installed when you install the OGM, so you will require the following dependencies: + +- `@neo4j/graphql-ogm` is the OGM package. +- `graphql` is the package used by the Neo4j GraphQL Library to generate a schema and execute queries and mutations. +- `neo4j-driver` is the official Neo4j Driver package for JavaScript, necessary for interacting with the database. + +[source, bash] +---- +npm install @neo4j/graphql-ogm graphql neo4j-driver +---- + +To use the OGM, it will need to be imported wherever you want to use it: + +[source, javascript] +---- +const { OGM } = require("@neo4j/graphql-ogm"); +---- + +It's recommended to check out the <> to see where you might go from here. diff --git a/docs/asciidoc/ogm/selection-set.adoc b/docs/asciidoc/ogm/selection-set.adoc index 979e7c4250..354f3fdbbc 100644 --- a/docs/asciidoc/ogm/selection-set.adoc +++ b/docs/asciidoc/ogm/selection-set.adoc @@ -1,7 +1,7 @@ [[ogm-selection-set]] = Selection Set -This is a GraphQL specific term. When you preform a query you have the operation; +This is a GraphQL specific term. When you execute a query, you have the operation: [source, graphql] ---- @@ -10,32 +10,41 @@ query { } ---- -And you also have a Selection Set; +And you also have a selection set. For example, from the example below: [source, graphql] ---- query { myOperation { - # Selection Set start - id - name - } # Selection Set end + field1 + field2 + } +} +---- + +The following snippet is the selection set: + +[source, graphql] +---- +{ + field1 + field2 } ---- -When using the OGM we do not want users providing a selections sets. Doing so would make the OGM feel more like querying the GraphQL Schema when the OGM is designed as an abstraction on top of it. To combat this we do Autogenerated Selection Sets. Given a Node; +When using the OGM we do not want users to have to provide a selection set by default. Doing so would make using the OGM feel more like querying the GraphQL schema directly, when the OGM is designed as an abstraction over it. To combat this, we automatically generate basic selection sets. Given the following type definition: [source, graphql] ---- type Node { id: ID name: String - relation: [Node] @relationship(...) - customCypher: [Node] @cypher(...) + relatedNodes: [Node] @relationship(type: "HAS_NODE", direction: OUT) + customCypher: String! @cypher(statement: "RETURN someCustomData") } ---- -We pre-generate a pre-defined selection set. We don't include any relationships or cypher fields, as they could be computationally expensive. Given the above Node the auto pre-defined selection set would be; +We don't include any relationship fields or Cypher fields in the generated selection set, as they could be computationally expensive. So, given the type definition above, the generated selection set would be: [source, graphql] ---- @@ -45,9 +54,11 @@ We pre-generate a pre-defined selection set. We don't include any relationships } ---- -This means that by default, querying for Node(s), you would only get the `.id` and `.name` properties returned. If you want to select more you can either define a selection set at execution time or as a static on the Model; +This means that by default, querying for Node(s), you would only get the `.id` and `.name` properties returned. If you want to select more fields, you can either define a selection set at execution time or as a static on the Model, as described below. + +== Selection set at execution time -=== Selection set at execution time +Using this approach, we would pass in a selection set every time we interact with the OGM. This would be an appropriate approach if the selection set is going to be different every time you ask for data. A full example of this would be as follows: [source, javascript] ---- @@ -63,8 +74,8 @@ const typeDefs = ` type Node { id: ID name: String - relation: [Node] @relationship(...) - customCypher: [Node] @cypher(...) + relatedNodes: [Node] @relationship(type: "HAS_NODE", direction: OUT) + customCypher: String! @cypher(statement: "RETURN someCustomData") } `; @@ -75,20 +86,22 @@ const selectionSet = ` { id name - relation { - id - name - } - customCypher { + relatedNodes { id name } + customCypher } `; + const nodes = await Node.find({ selectionSet }); ---- -=== Selection set as a static +Note that we are passing in the argument `selectionSet` into the `Node.find()` function. + +== Selection set as a static + +Using this approach, you can assign a selection set to a particular Model, so that whenever it is queried, it will always return those fields. This is useful if the default selection set doesn't quite give you enough data, but you don't need the selection set to be dynamic on each request. See a full example below: [source, javascript] ---- @@ -104,8 +117,8 @@ const typeDefs = ` type Node { id: ID name: String - relation: [Node] @relationship(...) - customCypher: [Node] @cypher(...) + relatedNodes: [Node] @relationship(type: "HAS_NODE", direction: OUT) + customCypher: String! @cypher(statement: "RETURN someCustomData") } `; @@ -116,15 +129,17 @@ const selectionSet = ` { id name - relation { - id - name - } - customCypher { + relatedNodes { id name } + customCypher } `; + Node.setSelectionSet(selectionSet) + +const nodes = await Node.find(); ---- + +Note that despite not passing this selection set into `Node.find()`, the requested fields will be returned on each request. diff --git a/docs/asciidoc/pagination/cursor-based.adoc b/docs/asciidoc/pagination/cursor-based.adoc index c027582cac..d0fd2fe06b 100644 --- a/docs/asciidoc/pagination/cursor-based.adoc +++ b/docs/asciidoc/pagination/cursor-based.adoc @@ -1,4 +1,89 @@ [[pagination-cursor-based]] = Cursor-based pagination -On relationship fields, you are able to take advantage of cursor-based pagination. +On relationship fields, you are able to take advantage of cursor-based pagination, which is often associated with infinitely-scrolling applications. + +Using the following type definition: + +[source, graphql] +---- +type User { + name: String! + posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) +} + +type Post { + content: String! +} +---- + +If you wanted to fetch the posts of user "John Smith" 10 at a time, you would first fetch 10: + +[source, graphql] +---- +query { + users(where: { name: "John Smith" }) { + name + postsConnection(first: 10) { + edges { + node { + content + } + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +---- + +In the return value, if `hasNextPage` is `true`, you would pass `endCursor` into the next query of 10. We would likely do this using a variable as in the following example: + +[source, graphql] +---- +query Users($after: String) { + users(where: { name: "John Smith" }) { + name + postsConnection(first: 10, after: $after) { + edges { + node { + content + } + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +---- + +You would continue doing this until `hasNextPage` if `false` - this is when you have reached the end of the data. + +== `totalCount` + +The Connection fields also offer a `totalCount` field which can be used to calculate page numbers, which is useful if you want to page by cursors but use page number in your application. Using the example above, you would simply add the `totalCount` field which will return the total number of results matching the filter used, which in this example would just be all posts: + +[source, graphql] +---- +query Users($after: String) { + users(where: { name: "John Smith" }) { + name + postsConnection(first: 10) { + edges { + node { + content + } + } + pageInfo { + endCursor + hasNextPage + } + totalCount + } + } +} +---- diff --git a/docs/asciidoc/pagination/offset-based.adoc b/docs/asciidoc/pagination/offset-based.adoc index 091a523fed..564849b1a5 100644 --- a/docs/asciidoc/pagination/offset-based.adoc +++ b/docs/asciidoc/pagination/offset-based.adoc @@ -1,7 +1,7 @@ [[pagination-offset-based]] = Offset-based pagination -Page-based pagination can be achieved through the use of the `offset` and `limit` options available whilst querying for data. +Offset-based pagination, often associated with navigation via pages, can be achieved through the use of the `offset` and `limit` options available whilst querying for data. Using the following type definition: @@ -27,7 +27,8 @@ query { And then on subsequent calls, introduce the `offset` argument and increment it by 10 on each call. -Page 2: +*Page 2:* + [source, graphql] ---- query { @@ -40,7 +41,8 @@ query { } ---- -Page 3: +*Page 3:* + [source, graphql] ---- query { @@ -53,10 +55,14 @@ query { } ---- +And so on, so forth. + == Total number of pages You can fetch the total number of records for a certain type using its count query, and then divide that number by your entries per page in order to calculate the total number of pages. This will allow to to determine what the last page is, and whether there is a next page. +See <> queries for details on how to execute these queries. + == Paginating relationship fields Say that in addition to the `User` type above, there is also a `Post` type which a `User` has many of. You can also fetch a `User` and then paginate through their posts: diff --git a/docs/asciidoc/queries.adoc b/docs/asciidoc/queries.adoc index c3196ece1a..5a74c75f05 100644 --- a/docs/asciidoc/queries.adoc +++ b/docs/asciidoc/queries.adoc @@ -1,12 +1,9 @@ [[queries]] = Queries -Each object defined in type definitions will have a Query field generated for it. Each Query field accepts two arguments: +Each node defined in type definitions will have two Query fields generated for it - one for querying data and one for counting data. -* `where`: Used to specify the <> which should be applied whilst querying the data -* `options`: Used to specify the options for <> and <> - -== Example +The examples in this chapter will use the following type definitions: Given the following type definitions: @@ -15,31 +12,38 @@ Given the following type definitions: type Post { id: ID! @id content: String! - creator: User @relationship(type: "HAS_POST", direction: IN) + creator: User! @relationship(type: "HAS_POST", direction: IN) } type User { id: ID! @id - name: String - posts: [Post] @relationship(type: "HAS_POST", direction: OUT) + name: String! + posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) } ---- -The following Query fields will be automatically generated: +For which the following Query fields will be generated: [source, graphql] ---- type Query { posts(where: PostWhere, options: PostOptions): [Post!]! + postsCount(where: PostWhere): Int! users(where: UserWhere, options: UserOptions): [User!]! - countPosts(where: PostWhere): Int! usersCount(where: UserWhere): Int! } ---- -=== Querying for all Users +== Query + +Each field for querying data accepts two arguments: + +- `where` - used for <> data +- `options` - used to specify <> and <> options -The following Query will return all Users, returning their ID and name. +=== Querying for all User nodes + +The following Query will return all User nodes, returning their ID and name. [source, graphql] ---- @@ -51,14 +55,16 @@ query { } ---- -=== Query for all Users' Posts +=== Query for user with name "Jane Smith" and their posts The following Query will return all Users, returning the content which they have posted. [source, graphql] ---- query { - users { + users(where: { name: "Jane Smith" }) { + id + name posts { content } @@ -66,10 +72,12 @@ query { } ---- +[[queries-count]] +== Count -=== Counting all Users +=== Counting all User nodes -The following Query will count all users and return a number. +The following Query will count all User nodes: [source, graphql] ---- @@ -78,6 +86,11 @@ query { } ---- -=== Filtering +=== Counting User nodes where name starts with "J" -See <> for details on how data can be filtered whilst querying. +[source, graphql] +---- +query { + usersCount(where: { name_STARTS_WITH: "J" }) +} +---- diff --git a/docs/asciidoc/troubleshooting/faqs.adoc b/docs/asciidoc/troubleshooting/faqs.adoc new file mode 100644 index 0000000000..36b539a67b --- /dev/null +++ b/docs/asciidoc/troubleshooting/faqs.adoc @@ -0,0 +1,16 @@ +[[troubleshooting-faqs]] += FAQs + +This chapter contains commonly asked questions and their solutions. + +== I've upgraded from <1.1.0 and my `DateTime` fields aren't sorting as expected + +Due to a bug in versions less than 1.1.0, there is a chance that your `DateTime` fields are stored in the database as strings instead of temporal values. You should perform a rewrite of those properties in your database using a Cypher query. For an example where the affected node has label "Movie" and the affected property is "timestamp", you can do this using the following Cypher: + +[source, javascript] +---- +MATCH (m:Movie) +WHERE apoc.meta.type(m.timestamp) = "STRING" +SET m.timestamp = datetime(m.timestamp) +RETURN m +---- diff --git a/docs/asciidoc/troubleshooting.adoc b/docs/asciidoc/troubleshooting/index.adoc similarity index 76% rename from docs/asciidoc/troubleshooting.adoc rename to docs/asciidoc/troubleshooting/index.adoc index 27c7f0090e..7a3c5819e2 100644 --- a/docs/asciidoc/troubleshooting.adoc +++ b/docs/asciidoc/troubleshooting/index.adoc @@ -1,6 +1,8 @@ [[troubleshooting]] = Troubleshooting +This chapter contains common troubleshooting steps. Additionally, there is a section for <> (Frequently Asked Questions) where you might find answers to your problems. + == Debug Logging `@neo4j/graphql` uses the https://www.npmjs.com/package/debug[`debug`] library for debug-level logging. You can turn on all debug logging by setting the environment variable `DEBUG` to `@neo4j/graphql:*` whilst running. For example: @@ -63,15 +65,3 @@ server.listen().then(({ url }) => { console.log(`Server ready at ${url}`); }); ---- - -== I've upgraded from <1.1.0 and my `DateTime` fields aren't sorting as expected! - -Due to a bug in versions less than 1.1.0, there is a chance that your `DateTime` fields are stored in the database as strings instead of temporal values. You should perform a rewrite of those properties in your database using a Cypher query. For an example where the affected node has label "Movie" and the affected property is "timestamp", you can do this using the following Cypher: - -[source, javascript] ----- -MATCH (m:Movie) -WHERE apoc.meta.type(m.timestamp) = "STRING" -SET m.timestamp = datetime(m.timestamp) -RETURN m ----- diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 8b570f2631..328e0506f3 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -120,8 +120,17 @@ - - + + + + + + + + + + + @@ -189,6 +198,9 @@ + + + From f4322cf50e8d00e9962fbc18050fdd8d69454c62 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Thu, 29 Jul 2021 17:15:57 +0100 Subject: [PATCH 18/20] Rework auth content --- docs/antora/content-nav.adoc | 5 +- docs/asciidoc/auth/auth-directive.adoc | 84 ++++++++++++++++++ docs/asciidoc/auth/authentication.adoc | 85 ++----------------- docs/asciidoc/auth/authorization/allow.adoc | 50 ++++++----- docs/asciidoc/auth/authorization/bind.adoc | 49 +++++------ docs/asciidoc/auth/authorization/index.adoc | 5 ++ docs/asciidoc/auth/authorization/roles.adoc | 15 ++-- docs/asciidoc/auth/authorization/where.adoc | 16 ++-- docs/asciidoc/auth/index.adoc | 14 ++- docs/asciidoc/auth/setup.adoc | 82 +++--------------- docs/asciidoc/directives.adoc | 2 +- .../ogm/examples/custom-resolvers.adoc | 49 +++++++++-- docs/docbook/content-map.xml | 15 ++-- 13 files changed, 237 insertions(+), 234 deletions(-) create mode 100644 docs/asciidoc/auth/auth-directive.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 95455112bc..0314e94b1e 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -23,12 +23,13 @@ ** xref:custom-resolvers/index.adoc[] ** xref:auth/index.adoc[] *** xref:auth/setup/index.adoc[] +*** xref:auth/auth-directive/index.adoc[] *** xref:auth/authentication/index.adoc[] *** xref:auth/authorization/index.adoc[] -**** xref:auth/authorization/roles/index.adoc[] **** xref:auth/authorization/allow/index.adoc[] -**** xref:auth/authorization/where/index.adoc[] **** xref:auth/authorization/bind/index.adoc[] +**** xref:auth/authorization/roles/index.adoc[] +**** xref:auth/authorization/where/index.adoc[] ** xref:directives/index.adoc[] ** xref:api-reference/index.adoc[] ** xref:ogm/index.adoc[] diff --git a/docs/asciidoc/auth/auth-directive.adoc b/docs/asciidoc/auth/auth-directive.adoc new file mode 100644 index 0000000000..417c4ba773 --- /dev/null +++ b/docs/asciidoc/auth/auth-directive.adoc @@ -0,0 +1,84 @@ +[[auth-directive]] += `@auth` directive + +The `@auth` directive definition is dynamically generated on runtime based on user type definitions. + +== `rules` + +You can have many rules for many operations. We fall through each rule, until we find a match to the corresponding operation. If no match is found, an error is thrown. You can think of rules as a big `OR`. + +[source, graphql] +---- +@auth(rules: [ + { operations: [CREATE, UPDATE], ... }, ## or + { operations: [READ, UPDATE], ...}, ## or + { operations: [DELETE, UPDATE], ... } ## or +]) +---- + +== `operations` + +`operations` is an array which allows you to re-use the same rule for many operations. + +[source, graphql] +---- +@auth(rules: [ + { operations: [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT] }, + { operations: [READ] } +]) +---- + +NOTE: Note that the absence of an `operations` argument will imply _all_ operations. + +Many different operations can be called at once, for example in the following Mutation: + +[source, graphql] +---- +mutation { + createPosts( + input: [ + { + content: "I like GraphQL", + creator: { connect: { where: { id: "user-01" } } } + } + ] + ) { + posts { + content + } + } +} +---- + +In the above example, we perform a `CREATE` followed by a `CONNECT`, so our auth rule must allow our user to perform both of these operations. + +The full list of operations and how they related to Cypher clauses are: + +|=== +|Operation |Cypher clause(s) + +|`READ` +|`MATCH` + +|`CREATE` +|`CREATE` + +|`UPDATE` +|`SET` + +|`DELETE` +|`DELETE` + +|`CONNECT` +|`MATCH` and `MERGE` + +|`DISCONNECT` +|`MATCH` and `DELETE` +|=== + +== Auth Value Plucking + +When using the `@auth` directive, you use the following prefixes to substitute in their relevant values: + +- `$jwt.` - pulls value from JWT +- `$context.` - pulls value from context diff --git a/docs/asciidoc/auth/authentication.adoc b/docs/asciidoc/auth/authentication.adoc index 9bc32dadf6..84a71aaf06 100644 --- a/docs/asciidoc/auth/authentication.adoc +++ b/docs/asciidoc/auth/authentication.adoc @@ -1,87 +1,13 @@ [[auth-authentication]] = Authentication -Neo4j GraphQL will expect there to be an `authorization` header in the request object, which means you can authenticate users however you like. You could; Have a custom sign-in mutation, integrate with Auth0, or roll your own SSO server. The point here is that it’s just a JWT, in the library, we will decode it to make sure it’s valid - but it’s down to you to issue tokens. +The Neo4j GraphQL Library expects an `authorization` header in the request object, which means you can authenticate users however you like. You could have a custom sign-in mutation, integrate with Auth0, or roll your own SSO server. The point here is that it’s just a JWT which the library decodes to make sure it’s valid - but it’s down to the user to issue tokens. -== OGM - -Here we will use the <> to set up a hypothetical sign-in flow; - - -[source, javascript] ----- -const { Neo4jGraphQL } = require("@neo4j-graphql"); -const { createJWT, comparePassword } = require("./utils"); // example -const { ApolloServer } = require("apollo-server"); -const { OGM } = require("@neo4j-graphql-ogm"); - -const typeDefs = ` - type User { - id: ID @id - email: String! - password: String! - } - - type Mutation { - signIn(email: String!, password: String!): String ## token - } -`; - -const driver = neo4j.driver("bolt://localhost:7687", neo4j.auth.basic("admin", "password")); - -const ogm = new OGM({ - typeDefs, - driver, -}); - -const User = ogm.model("User"); - -const resolvers = { - Mutation: { - async signIn(root, { email, password }) { - const [existing] = await User.find({ - where: { - email, - }, - }); - - if (!existing) { - throw new Error("not found"); - } - - const equal = await comparePassword(password, existing.password); - if (!equal) { - throw new Error("bad password"); - } - - return createJWT({ - sub: user.id, - }); - }, - }, -}; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - resolvers, - driver, - config: { - jwt: { - secret - } - } -}); - -const server = new ApolloServer({ - schema: neoSchema.schema, - context: ({ req }) => ({ req }), -}); - -server.listen(4000).then(() => console.log("online")); ----- +> The example at <> demonstrates a hypothetical sign-up/sign-in flow using the <>, which will be a good starting point for inspiration. == `isAuthenticated` -This is the most basic of auth. Used to ensure that there is a valid decoded JWT in the request. The most basic of applications could look something like this; + +This is the most basic of authentication, used to ensure that there is a valid decoded JWT in the request. The most basic of type definitions could look something like the following, which states you must be authenticated to access `Todo` objects: [source, graphql] ---- @@ -94,9 +20,10 @@ extend type Todo @auth(rules: [{ isAuthenticated: true }]) ---- == `allowUnauthenticated` + In some cases, you may want to allow unauthenticated requests while also having auth-based rules. You can use the `allowUnauthenticated` parameter to avoid throwing an exception if no auth is present in the context. -In the example below, only the publisher can see his blog posts if it is not published yet. Once the blog post is published, anyone can see it. +In the example below, only the publisher can see his blog posts if it is not published yet. Once the blog post is published, anyone can see it: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/allow.adoc b/docs/asciidoc/auth/authorization/allow.adoc index 58ec5f113b..d16eee76cc 100644 --- a/docs/asciidoc/auth/authorization/allow.adoc +++ b/docs/asciidoc/auth/authorization/allow.adoc @@ -1,14 +1,16 @@ [[auth-authorization-allow]] = Allow -Use `allow` to ensure that on matched nodes, a connection exists between a value on the JWT and a property on each matched node. Taking a closer look, let's create two users in a hypothetical empty database; +Use `allow` to ensure that on matched nodes, there is a match between a value on the JWT and a property on each matched node. Taking a closer look, let's create two users in a hypothetical empty database: [source, cypher] ---- -CREATE (:User {id:"user1", name: "one"}) -CREATE (:User {id:"user2", name: "two"}) +CREATE (:User { id: "user1", name: "one" }) +CREATE (:User { id: "user2", name: "two" }) ---- +For the node type above, we might have the following simple type definition: + [source, graphql] ---- type User { @@ -17,7 +19,7 @@ type User { } ---- -Now we have two users in our database, and given the above GraphQL type definitions - How can we restrict `user1` from accessing `user2`? This is where `allow` comes in; +Now we have two users in our database, and a simple type definition - we need to restrict `user1` from accessing `user2`? This is where `allow` comes in: [source, graphql] ---- @@ -36,18 +38,20 @@ extend type User @auth( ) ---- -After we match the node, we validate that the property on the node is equal to the `jwt.sub` property. This validation is done in Cypher with two functions; `validatePredicate` and `validate`. +After we match the node, we validate that the property `id` on the node is equal to the `jwt.sub` property. + +Given `user1` has the following decoded JWT: -Given `user1` has the decoded JWT; [source, json] ---- { - "sub": "user1", - "iat": 1516239022 + "sub": "user1", + "iat": 1516239022 } ---- -With this JWT makes a GraphQL query to get `user2`; +If "user1" used this JWT in a request for "user2": + [source, graphql] ---- query { @@ -57,26 +61,26 @@ query { } ---- -The generated cypher for this query would look like the below and throw you out the operation. +The generated cypher for this query would look like the following and throw you out the operation: [source, cypher] ---- -MATCH (u:User {id: "user2"}) +MATCH (u:User { id: "user2" }) CALL apoc.util.validate(NOT(u.id = "user1"), "Forbidden") RETURN u ---- -Allow is used on the following operations; +Allow is available on the following operations: -1. read -2. update -3. connect -4. disconnect -5. delete +- `READ` +- `UPDATE` +- `CONNECT` +- `DISCONNECT` +- `DELETE` -== `allow` Across Relationships +== `allow` across relationships -There may be a reason where you need to traverse across relationships to satisfy your auth implementation. One example of this could be "Grant update access to all Moderators of a Post"; +There may be a reason where you need to traverse across relationships to satisfy your authorization implementation. One example use case could be "grant update access to all Moderators of a Post": [source, graphql] ---- @@ -95,9 +99,9 @@ extend type Post @auth(rules: [ ]) ---- -When you specify allow on a relationship you can select fields on the referenced node. It's worth pointing out that allow on a relationship will perform an `ANY` on the matched nodes; to see if there is a match. +When you specify allow on a relationship you can select fields on the referenced node. It's worth pointing out that allow on a relationship will perform an `ANY` on the matched nodes to see if there is a match. -Given the above example - There may be a time when you need to give update access to either the creator of a post or a moderator, you can use `OR` and `AND` inside allow; +Given the above example - There may be a time when you need to give update access to either the creator of a post or a moderator, you can use `OR` and `AND` inside `allow`: [source, graphql] ---- @@ -123,9 +127,9 @@ extend type Post ) ---- -== Field Level `allow` +== Field-level `allow` -Allow works the same as it does on Type Definitions although its context is the Field. So instead of enforcing auth rules when the node is matched and/or upserted, it would instead be called when the Field is selected or upserted. Given the following, it is hiding the password to only the user themselves; +`allow` works the same as it does on Types although its context is the Field. So instead of enforcing auth rules when the node is matched and/or modified, it would instead be called when the Field is match and/or modified. Given the following, it is hiding the password to all users but the user themselves: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/bind.adoc b/docs/asciidoc/auth/authorization/bind.adoc index c294d58f59..d0c94027f9 100644 --- a/docs/asciidoc/auth/authorization/bind.adoc +++ b/docs/asciidoc/auth/authorization/bind.adoc @@ -1,13 +1,15 @@ [[auth-authorization-bind]] = Bind -Use bind to ensure that on creating or updating nodes, a connection exists between a value on the JWT vs a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, let's put a user in our database; +Use bind to ensure that on creating or updating nodes, there is a match between a value on the JWT and a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, let's create a user in our database: [source, cypher] ---- -CREATE (:User {id:"user1", name: "one"}) +CREATE (:User { id:"user1", name: "one" }) ---- +For the node type above, we might have the following simple type definition: + [source, graphql] ---- type User { @@ -16,8 +18,7 @@ type User { } ---- - -Given the above GraphQL type definitions - How can we restrict `user1` from changing their ID? +Given the above GraphQL type definition - we can restrict `user1` from changing their own ID: [source, graphql] ---- @@ -36,20 +37,19 @@ extend type User @auth( ) ---- -After we update or create the node we validate that the property on the node is equal to the `jwt.sub` property. This validation is done in Cypher with function `apoc.util.validate` +After we update or create the node we validate that the property `id` on the node is equal to the `jwt.sub` property. -Given `user1` has the decoded JWT; +Given `user1` has the following decoded JWT: [source, json] ---- { - "sub": "user1", - "iat": 1516239022 + "sub": "user1", + "iat": 1516239022 } ---- -With this JWT makes a GraphQL mutation to update their ID to someone else; - +When the user makes a request using this JWT to change their ID: [source, graphql] ---- @@ -62,30 +62,27 @@ mutation { } ---- -The generated cypher for this query would look like the below, Throwing us out of the operation because the ids do not match. - +The generated cypher for this query would look like the below, throwing us out of the operation because the `id` property no longer matches. [source, cypher] ---- -MATCH (u:User {id: "user1"}) +MATCH (u:User { id: "user1" }) SET u.id = "user2" CALL apoc.util.validate(NOT(u.id = "user1"), "Forbidden") RETURN u ---- +Bind is available for the following operations; -Bind is used on the following operations; - -1. create -2. update -3. connect -4. disconnect -5. delete - +- `READ` +- `UPDATE` +- `CONNECT` +- `DISCONNECT` +- `DELETE` -== `bind` Across Relationships +== `bind` across relationships -There may be a reason where you need to traverse across relationships to satisfy your Auth implementation. One example of this could be "Ensure that users only create Posts related to themselves"; +There may be a reason where you need to traverse across relationships to satisfy your authorization implementation. One use case could be "ensure that users only create Posts related to themselves": [source, graphql] ---- @@ -104,11 +101,11 @@ extend type Post @auth(rules: [ ]) ---- -When you specify `bind` on a relationship you can select fields on the referenced node. It's worth pointing out that allow on a relationship will perform an `ALL` on the matched nodes; to see if there is a match. This means you can only use `bind` to enforce a single relationship to a single node. +When you specify `bind` on a relationship you can select fields on the related node. It's worth pointing out that `bind` on a relationship field will perform an `ALL` on the matched nodes to see if there is a match. This means you can only use `bind` to enforce a single relationship to a single node. -=== Field Level `bind` +=== Field-level `bind` -You can use bind on a field. The root is still considered the node. Taking the example at the start of this `bind` section; you could do the following; +You can use `bind` on a field, and the root is still considered the node itself. Taking the example at the start of this chapter, you could do the following to implement the same behaviour: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/index.adoc b/docs/asciidoc/auth/authorization/index.adoc index 4e1a01ff76..f33493f68c 100644 --- a/docs/asciidoc/auth/authorization/index.adoc +++ b/docs/asciidoc/auth/authorization/index.adoc @@ -2,3 +2,8 @@ = Authorization You specify authorization rules inside the `@auth` directive. This section looks at each option available and explains how to use it to implement authorization. + +- <> +- <> +- <> +- <> diff --git a/docs/asciidoc/auth/authorization/roles.adoc b/docs/asciidoc/auth/authorization/roles.adoc index 50f52d5cf7..2dd898ff3b 100644 --- a/docs/asciidoc/auth/authorization/roles.adoc +++ b/docs/asciidoc/auth/authorization/roles.adoc @@ -1,7 +1,9 @@ [[auth-authorization-roles]] = Roles -Use the roles property to specify the allowed roles for an operation. Use config `rolesPath` to specify a object path for JWT roles otherwise defaults to `jwt.roles` +Use the `roles` property to specify the allowed roles for an operation. Use the `Neo4jGraphQL` config option `rolesPath` to specify a object path for JWT roles otherwise defaults to `jwt.roles`. + +The following type definitions show that an admin role is required for all update operations against Users. [source, graphql] ---- @@ -13,19 +15,16 @@ type User { extend type User @auth(rules: [{ operations: [UPDATE], roles: ["admin"] }]) ---- -Above showing an admin role is required for all operations against Users. If you have multiple roles you can add more items to the array; +If there are multiple possible roles you can add more items to the array, of which users only need one to satisfy a rule: [source, graphql] ---- -extend type User @auth(rules: [{ roles: ["admin", "super-admin"] }]) +extend type User @auth(rules: [{ operations: [UPDATE], roles: ["admin", "super-admin"] }]) ---- - -> Users only need one of many roles to satisfy a rule. - == RBAC -Here is a RBAC example using `roles`; +Here is an example of RBAC (Role-Based Access Control) using `roles`: [source, graphql] ---- @@ -45,4 +44,4 @@ type Invoice @auth(rules: [{ operations: [READ], roles: ["read:invoice"] }]) { csv: String total: Int } ----- \ No newline at end of file +---- diff --git a/docs/asciidoc/auth/authorization/where.adoc b/docs/asciidoc/auth/authorization/where.adoc index db187bf685..8baf031d78 100644 --- a/docs/asciidoc/auth/authorization/where.adoc +++ b/docs/asciidoc/auth/authorization/where.adoc @@ -1,7 +1,7 @@ [[auth-authorization-where]] = Where -Use the `where` argument, on Node definitions, to conceptually append predicates to the Cypher `WHERE` clause. Given the current user ID is "123" and the following the schema; +Use the `where` argument on types to conceptually append predicates to the Cypher `WHERE` clause. Given the current user ID is "123" and the following schema: [source, graphql] ---- @@ -13,7 +13,7 @@ type User { extend type User @auth(rules: [{ where: { id: "$jwt.id" } }]) ---- -Then issues a GraphQL query for users; +Then the user executes a GraphQL query for all users: [source, graphql] ---- @@ -25,7 +25,7 @@ query { } ---- -Behind the scenes the user’s ID is **conceptually** prepended to the query; +Behind the scenes the user’s ID is conceptually added to the query: [source, graphql] ---- @@ -39,8 +39,8 @@ query { Where is used on the following operations; -1. read -2. update -3. connect -4. disconnect -5. delete +- `READ` +- `UPDATE` +- `CONNECT` +- `DISCONNECT` +- `DELETE` diff --git a/docs/asciidoc/auth/index.adoc b/docs/asciidoc/auth/index.adoc index 0578275d94..9b9f0feae2 100644 --- a/docs/asciidoc/auth/index.adoc +++ b/docs/asciidoc/auth/index.adoc @@ -1,10 +1,16 @@ [[auth]] = Auth -In this section you will learn more about how to secure your GraphQL API using Neo4j GraphQL's inbuilt auth mechanics. +In this chapter you will learn more about how to secure your GraphQL API using the Neo4j GraphQL Library's built-in auth mechanics. -== Preview +- <> +- <> +- <> +- <> +== Quickstart examples + +Only authenticated users can create Post nodes: [source, graphql] ---- @@ -15,7 +21,7 @@ type Post @auth(rules: [ } ---- -When you have production-style Auth the directive can get large and complicated. Use Extend to tackle this; +Use `extend` to avoid large and unwieldy type definitions: [source, graphql] ---- @@ -28,7 +34,7 @@ extend type Post @auth(rules: [ ]) ---- -You can use the directive on 'Type Definitions', as seen in the example above, you can also apply the directive on any field so as long as it's not a `@relationship`; +You can use the directive types as seen in the example above, but you can also apply the directive on any field so as long as it's not decorated with `@relationship`. In the following example, the password field is only accessible to users with role "admin", or the user themselves: [source, graphql] ---- diff --git a/docs/asciidoc/auth/setup.adoc b/docs/asciidoc/auth/setup.adoc index debfa0b109..1bd6f229a7 100644 --- a/docs/asciidoc/auth/setup.adoc +++ b/docs/asciidoc/auth/setup.adoc @@ -23,9 +23,10 @@ const neoSchema = new Neo4jGraphQL({ }); ---- -It is also possible to pass in JWTs which have already been decoded, in which case the `jwt` option is _not necessary_. This will be covered in the section <>. +It is also possible to pass in JWTs which have already been decoded, in which case the `jwt` option is _not necessary_. This is covered in the section <> below. === Auth Roles Object Paths + If you are using a 3rd party auth provider such as Auth0 you may find your roles property being nested inside an object: [source, json] @@ -55,7 +56,7 @@ const neoSchema = new Neo4jGraphQL({ [[auth-setup-passing-in]] == Passing in JWTs -If you wish to pass in an encoded JWT, this must be included in the `Authorization` header of your requests, in the format: +If you wish to pass in an encoded JWT, this must be included in the `authorization` header of your requests, in the format: [source] ---- @@ -111,72 +112,7 @@ const server = new ApolloServer({ }); ---- -== `@auth` directive - -=== `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. - -[source, graphql] ----- -@auth(rules: [ - { operations: [CREATE, UPDATE], ... }, ## or - { operations: [READ, UPDATE], ...}, ## or - { operations: [DELETE, UPDATE], ... } ## or -]) ----- - -=== `operations` - -Operations is an array which allows you to re-use the same rule for many operations. - -[source, graphql] ----- -@auth(rules: [ - { operations: [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT] }, - { operations: [READ] } -]) ----- - -NOTE: Note that the absence of an `operations` argument will imply _all_ operations. - -Many different operations can be called at once, for example in the following Mutation: - -[source, graphql] ----- -mutation { - createPosts( - input: [ - { - content: "I like GraphQL", - creator: { connect: { where: { id: "user-01" } } } - } - ] - ) { - posts { - content - } - } -} ----- - -In the above example, we perform a `CREATE` followed by a `CONNECT`, so our auth rule must allow our user to perform both of these operations. - -The full list of operations are: - -- read - `MATCH` -- create - `CREATE` -- update - `SET` -- delete - `DELETE` -- connect - `MATCH` & `MERGE` -- disconnect - `MATCH` & `DELETE` - -== Auth Value Plucking - -1. `$jwt.` - Pulls value from jsonwebtoken -2. `$context.` - Pulls value from context - -== Auth Custom Resolvers +== Auth and Custom Resolvers You can't use the `@auth` directive on a custom resolver, however, we do make life easier by injecting the auth parameter into it. It will be available under the `context.auth` property. For example, the following custom resolver returns the `sub` field from the JWT: @@ -190,16 +126,16 @@ const typeDefs = ` const resolvers = { Query: { - myId(root, args, context) { + myId(_source, _args, context) { return context.auth.jwt.sub } } }; ---- -== Auth on `@cypher` +== Auth and `@cypher` fields -You can put the `@auth` directive on a field with the `@cypher` directive. Functionality like `allow` and `bind` will not work but you can still utilize `isAuthenticated` and `roles`. Additionally, you don't need to specify operations for `@auth` directives on `@cypher` fields. +You can put the `@auth` directive on a field alongside the `@cypher` directive. Functionality like `allow` and `bind` will not work but you can still utilize `isAuthenticated` and `roles`. Additionally, you don't need to specify `operations` for `@auth` directives on `@cypher` fields. The following example uses the `isAuthenticated` rule to ensure a user is authenticated, before returning the `User` associated with the JWT: @@ -211,7 +147,9 @@ type User @exclude { } type Query { - me: User @cypher(statement: "MATCH (u:User { id: $auth.jwt.sub }) RETURN u") @auth(rules: [{ isAuthenticated: true }]) + me: User + @cypher(statement: "MATCH (u:User { id: $auth.jwt.sub }) RETURN u") + @auth(rules: [{ isAuthenticated: true }]) } ---- diff --git a/docs/asciidoc/directives.adoc b/docs/asciidoc/directives.adoc index 78cbb7de38..1a009d334e 100644 --- a/docs/asciidoc/directives.adoc +++ b/docs/asciidoc/directives.adoc @@ -5,7 +5,7 @@ The `@auth` directive is used to define complex fine-grained and role-based access control for object types and fields. -Reference: <> +Reference: <> == `@coalesce` diff --git a/docs/asciidoc/ogm/examples/custom-resolvers.adoc b/docs/asciidoc/ogm/examples/custom-resolvers.adoc index 240d952d5a..d2403c537a 100644 --- a/docs/asciidoc/ogm/examples/custom-resolvers.adoc +++ b/docs/asciidoc/ogm/examples/custom-resolvers.adoc @@ -29,6 +29,8 @@ const { OGM } = require("@neo4j/graphql-ogm"); const { ApolloServer } = require("apollo-server"); const neo4j = require("neo4j-driver"); +const { createJWT, comparePassword } = require("./utils"); // example util functions + const driver = neo4j.driver( "bolt://localhost:7687", neo4j.auth.basic("neo4j", "password") @@ -42,7 +44,8 @@ const typeDefs = ` } type Mutation { - createUser(username: String!, password: String!): User! + signUp(username: String!, password: String!): String! ### JWT + signIn(username: String!, password: String!): String! ### JWT } `; @@ -51,8 +54,18 @@ const User = ogm.model("User"); const resolvers = { Mutation: { - createUser(_source, { username, password }): async () => { - const { users } = await User.create({ + signUp: async (_source, { username, password }) => { + const [existing] = await User.find({ + where: { + username, + }, + }); + + if (existing) { + throw new Error(`User with username ${username} already exists!`); + } + + const [user] = await User.create({ input: [ { username, @@ -61,7 +74,26 @@ const resolvers = { ] }); - return users[0]; + return createJWT({ sub: user.id }); + }, + signIn: async (_source, { email, password }) => { + const [user] = await User.find({ + where: { + username, + }, + }); + + if (!user) { + throw new Error(`User with username ${username} not found!`); + } + + const correctPassword = await comparePassword(password, user.password); + + if (!correctPassword) { + throw new Error(`Incorrect password for user with username ${username}!`); + } + + return createJWT({ sub: user.id }); }, }, }; @@ -69,6 +101,11 @@ const resolvers = { const neoSchema = new Neo4jGraphQL({ typeDefs, resolvers, + config: { + jwt: { + secret: "secret", + }, + }, }); const server = new ApolloServer({ @@ -80,6 +117,8 @@ server.listen().then(({ url }) => { }); ---- +It's important to note the JWT secret being passed into the `Neo4jGraphQL` constructor in this example. + Back in the command line, run the following command to start your server: [source, bash] @@ -94,4 +133,4 @@ You should see the following output: 🚀 Server ready at http://localhost:4000/ ---- -You can execute the `createUser` Mutation against this GraphQL API, but when you go to query the user through the same API, the password field will not be available. +You can execute the `signUp` Mutation against this GraphQL API to sign up, but when you go to query the user through the same API, the password field will not be available. diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 328e0506f3..36cf42539b 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -87,6 +87,9 @@ + + + @@ -95,18 +98,18 @@ - - - - - - + + + + + + From f8de5f2813af9477baba3526af786b105167a02e Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Fri, 30 Jul 2021 11:42:50 +0100 Subject: [PATCH 19/20] Overhaul API references for both Neo4jGraphQL and OGM --- docs/antora/content-nav.adoc | 15 +- docs/asciidoc/api-reference.adoc | 41 ---- docs/asciidoc/api-reference/index.adoc | 5 + docs/asciidoc/api-reference/neo4jgraphql.adoc | 194 ++++++++++++++++++ docs/asciidoc/api-reference/ogm.adoc | 4 + docs/asciidoc/filtering.adoc | 1 + docs/asciidoc/ogm/api-reference.adoc | 62 ------ docs/asciidoc/ogm/api-reference/index.adoc | 5 + .../ogm/api-reference/model/count.adoc | 35 ++++ .../ogm/api-reference/model/create.adoc | 48 +++++ .../ogm/api-reference/model/delete.adoc | 57 +++++ .../ogm/api-reference/model/find.adoc | 62 ++++++ .../ogm/api-reference/model/index.adoc | 22 ++ .../ogm/api-reference/model/update.adoc | 76 +++++++ docs/asciidoc/ogm/api-reference/ogm.adoc | 48 +++++ docs/asciidoc/ogm/methods/count.adoc | 20 -- docs/asciidoc/ogm/methods/create.adoc | 30 --- docs/asciidoc/ogm/methods/delete.adoc | 29 --- docs/asciidoc/ogm/methods/find.adoc | 56 ----- docs/asciidoc/ogm/methods/index.adoc | 12 -- docs/asciidoc/ogm/methods/update.adoc | 45 ---- docs/asciidoc/troubleshooting/index.adoc | 1 + docs/docbook/content-map.xml | 50 +++-- 23 files changed, 599 insertions(+), 319 deletions(-) delete mode 100644 docs/asciidoc/api-reference.adoc create mode 100644 docs/asciidoc/api-reference/index.adoc create mode 100644 docs/asciidoc/api-reference/neo4jgraphql.adoc create mode 100644 docs/asciidoc/api-reference/ogm.adoc delete mode 100644 docs/asciidoc/ogm/api-reference.adoc create mode 100644 docs/asciidoc/ogm/api-reference/index.adoc create mode 100644 docs/asciidoc/ogm/api-reference/model/count.adoc create mode 100644 docs/asciidoc/ogm/api-reference/model/create.adoc create mode 100644 docs/asciidoc/ogm/api-reference/model/delete.adoc create mode 100644 docs/asciidoc/ogm/api-reference/model/find.adoc create mode 100644 docs/asciidoc/ogm/api-reference/model/index.adoc create mode 100644 docs/asciidoc/ogm/api-reference/model/update.adoc create mode 100644 docs/asciidoc/ogm/api-reference/ogm.adoc delete mode 100644 docs/asciidoc/ogm/methods/count.adoc delete mode 100644 docs/asciidoc/ogm/methods/create.adoc delete mode 100644 docs/asciidoc/ogm/methods/delete.adoc delete mode 100644 docs/asciidoc/ogm/methods/find.adoc delete mode 100644 docs/asciidoc/ogm/methods/index.adoc delete mode 100644 docs/asciidoc/ogm/methods/update.adoc diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 0314e94b1e..d6881f8f3f 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -32,20 +32,23 @@ **** xref:auth/authorization/where/index.adoc[] ** xref:directives/index.adoc[] ** xref:api-reference/index.adoc[] +*** xref:api-reference/neo4jgraphql/index.adoc[] +*** xref:api-reference/ogm/index.adoc[] ** xref:ogm/index.adoc[] *** xref:ogm/installation/index.adoc[] *** xref:ogm/examples/index.adoc[] **** xref:ogm/examples/custom-resolvers/index.adoc[] **** xref:ogm/examples/rest-api/index.adoc[] -*** xref:ogm/methods/index.adoc[] -**** xref:ogm/methods/create/index.adoc[] -**** xref:ogm/methods/find/index.adoc[] -**** xref:ogm/methods/count/index.adoc[] -**** xref:ogm/methods/update/index.adoc[] -**** xref:ogm/methods/delete/index.adoc[] *** xref:ogm/private/index.adoc[] *** xref:ogm/selection-set/index.adoc[] *** xref:ogm/api-reference/index.adoc[] +**** xref:ogm/api-reference/ogm/index.adoc[] +**** xref:ogm/api-reference/model/index.adoc[] +***** xref:ogm/api-reference/model/create/index.adoc[] +***** xref:ogm/api-reference/model/find/index.adoc[] +***** xref:ogm/api-reference/model/update/index.adoc[] +***** xref:ogm/api-reference/model/delete/index.adoc[] +***** xref:ogm/api-reference/model/count/index.adoc[] ** xref:driver-configuration/index.adoc[] ** xref:guides/index.adoc[] *** xref:guides/migration-guide/index.adoc[] diff --git a/docs/asciidoc/api-reference.adoc b/docs/asciidoc/api-reference.adoc deleted file mode 100644 index dcb8c52be5..0000000000 --- a/docs/asciidoc/api-reference.adoc +++ /dev/null @@ -1,41 +0,0 @@ -[[api-reference]] -= API Reference - - -== `Neo4jGraphQL` -Main Entry to the library. Holds metadata about the GraphQL schema. - -=== Requiring -[source, javascript] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); ----- - -=== Constructing - -[source, javascript] ----- -const neo4jGraphQL = new Neo4jGraphQL({ - typeDefs, - resolvers?, - schemaDirectives?, - driver?, - config?: { - driverConfig?, - enableRegex?, - jwt?: { - secret?, - noVerify?, - rolesPath?, - }, - }, -}); ----- - -=== Methods - -==== `checkNeo4jCompat` -Reference: <> - -== `OGM` -Reference: <> diff --git a/docs/asciidoc/api-reference/index.adoc b/docs/asciidoc/api-reference/index.adoc new file mode 100644 index 0000000000..b8b6f14d12 --- /dev/null +++ b/docs/asciidoc/api-reference/index.adoc @@ -0,0 +1,5 @@ +[[api-reference]] += API Reference + +- <> +- <> diff --git a/docs/asciidoc/api-reference/neo4jgraphql.adoc b/docs/asciidoc/api-reference/neo4jgraphql.adoc new file mode 100644 index 0000000000..77e775edb4 --- /dev/null +++ b/docs/asciidoc/api-reference/neo4jgraphql.adoc @@ -0,0 +1,194 @@ +[[api-reference-neo4jgraphql]] += `Neo4jGraphQL` + +== `constructor` + +Returns a `Neo4jGraphQL` instance. + +Takes an `input` object as a parameter, the supported fields of which are described below. + +=== Example + +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, +}); +---- + +[[api-reference-neo4jgraphql-input]] +=== Input + +Accepts all of the options from https://github.com/ardatan/graphql-tools/blob/%40graphql-tools/schema%407.1.5/website/docs/generate-schema.md#makeexecutableschemaoptions[`makeExecutableSchema`], plus the additional arguments below: + +|=== +|Name and Type |Description + +|`driver` + + + + Type: https://neo4j.com/docs/javascript-manual/current/[`Driver`] +|An instance of a Neo4j driver. + +|`config` + + + + Type: `Neo4jGraphQLConfig` +|Additional Neo4j GraphQL configuration options. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig]] +==== `Neo4jGraphQLConfig` + +|=== +|Name and Type |Description + +|`driverConfig` + + + + Type: <> +|Additional driver configuration options. + +|`enableRegex` + + + + Type: `boolean` +|Whether to enable RegEx filters, see <> for more information. + +|`jwt` + + + + Type: <> +|JWT options. + +|`queryOptions` + + + + Type: <> +|Cypher query options, see <> for more information. + +|`skipValidateTypeDefs` + + + + Type: `boolean` +|Can be used to disable strict type definition validation if you are encountering unexpected errors. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-driverconfig]] +===== `DriverConfig` + +|=== +|Name and Type |Description + +|`database` + + + + Type: `string` +|The name of the database within the DBMS to connect to. + +|`bookmarks` + + + + Type: `string` or `Array` +|One or more bookmarks to use for the connection. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-neo4jgraphqljwt]] +===== `Neo4jGraphQLJWT` + +|=== +|Name and Type |Description + +|`secret` + + + + Type: `string` +|The secret used to encode JWT tokens. + + + + *Required* unless passing in decoded tokens. + +|`noVerify` + + + + Type: `boolean` +|Disable verification of JWT signatures, only decode. + +|`rolesPath` + + + + Type: `string` +|Dot path of location of roles within JWT token. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-cypherqueryoptions]] +===== `CypherQueryOptions` + +All options are enum types imported from `@neo4j/graphql`, for example: + +[source, javascript] +---- +const { CypherRuntime } = require("@neo4j/graphql"); +---- + +|=== +|Name and Type |Description + +|`runtime` + + + + Type: `CypherRuntime` +|Possible options: + + + + - `CypherRuntime.INTERPRETED` + + - `CypherRuntime.SLOTTED` + + - `CypherRuntime.PIPELINED` + +|`planner` + + + + Type: `CypherPlanner` +|Possible options: + + + + - `CypherPlanner.COST` + + - `CypherPlanner.IDP` + + - `CypherPlanner.DP` + +|`connectComponentsPlanner` + + + + Type: `CypherConnectComponentsPlanner` +|Possible options: + + + + - `CypherConnectComponentsPlanner.GREEDY` + + - `CypherConnectComponentsPlanner.IDP` + +|`updateStrategy` + + + + Type: `CypherUpdateStrategy` +|Possible options: + + + + - `CypherUpdateStrategy.DEFAULT` + + - `CypherUpdateStrategy.EAGER` + +|`expressionEngine` + + + + Type: `CypherExpressionEngine` +|Possible options: + + + + - `CypherExpressionEngine.DEFAULT` + + - `CypherExpressionEngine.INTERPRETED` + + - `CypherExpressionEngine.COMPILED` + +|`operatorEngine` + + + + Type: `CypherOperatorEngine` +|Possible options: + + + + - `CypherOperatorEngine.DEFAULT` + + - `CypherOperatorEngine.INTERPRETED` + + - `CypherOperatorEngine.COMPILED` + +|`interpretedPipesFallback` + + + + Type: `CypherInterpretedPipesFallback` +|Possible options: + + + + - `CypherInterpretedPipesFallback.DEFAULT` + + - `CypherInterpretedPipesFallback.DISABLED` + + - `CypherInterpretedPipesFallback.WHITELISTED_PLANS_ONLY` + + - `CypherInterpretedPipesFallback.ALL` + +|`replan` + + + + Type: `CypherReplanning` +|Possible options: + + + + - `CypherReplanning.DEFAULT` + + - `CypherReplanning.FORCE` + + - `CypherReplanning.SKIP` +|=== diff --git a/docs/asciidoc/api-reference/ogm.adoc b/docs/asciidoc/api-reference/ogm.adoc new file mode 100644 index 0000000000..de1f6345a6 --- /dev/null +++ b/docs/asciidoc/api-reference/ogm.adoc @@ -0,0 +1,4 @@ +[[api-reference-ogm]] += `@neo4j/graphql-ogm` + +See <>. diff --git a/docs/asciidoc/filtering.adoc b/docs/asciidoc/filtering.adoc index c6371bd3be..9e5e2bce15 100644 --- a/docs/asciidoc/filtering.adoc +++ b/docs/asciidoc/filtering.adoc @@ -32,6 +32,7 @@ The following case-sensitive comparison operators are only available for use on * `_CONTAINS` * `_NOT_CONTAINS` +[[filtering-regex]] ==== RegEx matching The filter `_MATCHES` is also available for comparison of `String` and `ID` types, which accepts a RegEx string as an argument and returns any matches. diff --git a/docs/asciidoc/ogm/api-reference.adoc b/docs/asciidoc/ogm/api-reference.adoc deleted file mode 100644 index cabcfa5d4f..0000000000 --- a/docs/asciidoc/ogm/api-reference.adoc +++ /dev/null @@ -1,62 +0,0 @@ -[[ogm-api-reference]] -= API Reference - -[[ogm-api-reference-ogm]] -== `OGM` - -=== Requiring -[source, javascript] ----- -const { OGM } = require("@neo4j/graphql-ogm"); ----- - -=== Constructing - -[source, javascript] ----- -const ogm = new OGM({ - typeDefs, - resolvers?, -}); ----- - -=== Methods - -==== `model()` -Reference: <> - -[[ogm-api-reference-model]] -== `Model` - -=== Requiring -[source, typescript] ----- -import type { Model } from "@neo4j/graphql-ogm" ----- - -=== Constructing - -You construct a model from invoking the `.model` method on an <>. - -[source, javascript] ----- -const model = ogm.model("name") ----- - -=== Methods - -==== `find()` -Reference: <> - -==== `count()` -Reference: <> - -==== `create()` -Reference: <> - -==== `update()` -Reference: <> - -==== `delete()` -Reference: <> - diff --git a/docs/asciidoc/ogm/api-reference/index.adoc b/docs/asciidoc/ogm/api-reference/index.adoc new file mode 100644 index 0000000000..1c6371241c --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/index.adoc @@ -0,0 +1,5 @@ +[[ogm-api-reference]] += API Reference + +- <> +- <> diff --git a/docs/asciidoc/ogm/api-reference/model/count.adoc b/docs/asciidoc/ogm/api-reference/model/count.adoc new file mode 100644 index 0000000000..43b4d6c099 --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/count.adoc @@ -0,0 +1,35 @@ +[[ogm-api-reference-model-count]] += `count` + +Returns a `Promise` that resolvers to the count of nodes based on the arguments passed in. + +== Example + +To query for all User nodes: + +[source, javascript] +---- +const User = ogm.model("User"); + +const usersCount = await User.count(); +---- + +To query for User nodes where name starts with the letter "D": + +[source, javascript] +---- +const User = ogm.model("User"); + +const usersCount = await User.count({ where: { name_STARTS_WITH: "D" }}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/create.adoc b/docs/asciidoc/ogm/api-reference/model/create.adoc new file mode 100644 index 0000000000..dc4acf19ec --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/create.adoc @@ -0,0 +1,48 @@ +[[ogm-api-reference-model-create]] += `create` + +This method can be used to update nodes, and maps to the underlying <> Mutation. + +Returns a `Promise` that resolves to the equivalent of the Mutation response for this operation. + +== Example + +To create a Movie with title "The Matrix": + +[source, javascript] +---- +const Movie = ogm.model("Movie"); + +await Movie.create({ input: [{ title: "The Matrix" }] }) +---- + +== Arguments + +|=== +|Name and Type |Description + +|`input` + + + + Type: `any` +|JavaScript object representation of the GraphQL `input` input type used for <> mutations. + +|`selectionSet` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|Selection set for the Mutation, see <> for more information. + +|`args` + + + + Type: `any` +|The `args` value for the GraphQL Mutation. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/delete.adoc b/docs/asciidoc/ogm/api-reference/model/delete.adoc new file mode 100644 index 0000000000..ecca36d2d4 --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/delete.adoc @@ -0,0 +1,57 @@ +[[ogm-api-reference-model-delete]] += `delete` + +This method can be used to delete nodes, and maps to the underlying <> Mutation. + +Returns a `Promise` which resolvers to a `DeleteInfo` object: + +|=== +|Name and Type |Description + +|`nodesDeleted` + + + + Type: `number` +|The number of nodes deleted. + +|`relationshipsDeleted` + + + + Type: `number` +|The number of relationships deleted. +|=== + +== Example + +To delete all User nodes where the name is "Dan": + +[source, javascript] +---- +const User = ogm.model("User"); + +await User.delete({ where: { name: "Dan" }}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. + +|`delete` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|A JavaScript object representation of the GraphQL `delete` input type used for <> Mutations. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/find.adoc b/docs/asciidoc/ogm/api-reference/model/find.adoc new file mode 100644 index 0000000000..5484a9604e --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/find.adoc @@ -0,0 +1,62 @@ +[[ogm-api-reference-model-find]] += `find` + +This method can be used to find nodes, and maps to the underlying schema <>. + +Returns a `Promise` which resolvers to an array of objects matching the type of the Model. + +== Example + +To find all user nodes in the database: + +[source, javascript] +---- +const User = ogm.model("User"); + +const users = await User.find(); +---- + +To find users with name "Jane Smith": + +[source, javascript] +---- +const User = ogm.model("User"); + +const users = await User.find({ where: { name: "Jane Smith" }}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. + +|`options` + + + + Type: `GraphQLOptionsArg` +|A JavaScript object representation of the GraphQL `options` input type used for <> and <>. + +|`selectionSet` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|Selection set for the Mutation, see <> for more information. + +|`args` + + + + Type: `any` +|The `args` value for the GraphQL Mutation. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/index.adoc b/docs/asciidoc/ogm/api-reference/model/index.adoc new file mode 100644 index 0000000000..552c97c272 --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/index.adoc @@ -0,0 +1,22 @@ +[[ogm-api-reference-model]] += `Model` + +== `create` + +See <>. + +== `find` + +See <>. + +== `update` + +See <>. + +== `delete` + +See <>. + +== `count` + +See <>. diff --git a/docs/asciidoc/ogm/api-reference/model/update.adoc b/docs/asciidoc/ogm/api-reference/model/update.adoc new file mode 100644 index 0000000000..b35a2c2edb --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/update.adoc @@ -0,0 +1,76 @@ +[[ogm-api-reference-model-update]] += `update` + +This method can be used to update nodes, and maps to the underlying <> Mutation. + +Returns a `Promise` that resolves to the equivalent of the Mutation response for this operation. + +== Example + +For the User with name "John", update their name to be "Jane": + +[source, javascript] +---- +const User = ogm.model("User"); + +const { users } = await User.update({ + where: { name: "John" }, + update: { name: "Jane" }, +}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. + +|`update` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `update` input type used for <> Mutations. + +|`connect` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `connect` input type used for <> Mutations. + +|`disconnect` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `disconnect` input type used for <> Mutations. + +|`create` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `create` input type used for <> Mutations. + +|`options` + + + + Type: `GraphQLOptionsArg` +|A JavaScript object representation of the GraphQL `options` input type used for <> and <>. + +|`selectionSet` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|Selection set for the Mutation, see <> for more information. + +|`args` + + + + Type: `any` +|The `args` value for the GraphQL Mutation. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/ogm.adoc b/docs/asciidoc/ogm/api-reference/ogm.adoc new file mode 100644 index 0000000000..73e761fafd --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/ogm.adoc @@ -0,0 +1,48 @@ +[[ogm-api-reference-ogm]] += `OGM` + +== `constructor` + +Returns an `OGM` instance. + +Takes an `input` object as a parameter, which is then passed to the `Neo4jGraphQL` constructor. Supported options are listed in the documentation for <>. + +=== Example + +[source, javascript] +---- +const ogm = new OGM({ + typeDefs, +}); +---- + +== `model` + +Returns a `Model` instance matching the passed in name, or throws an `Error` if one can't be found. + +Accepts a single argument `name` of type `string`. + +=== Example + +For the following type definitions: + +[source, graphql] +---- +type User { + username: String! +} +---- + +The following would successfully return a `Model` instance: + +[source, javascript] +---- +const User = ogm.model("User"); +---- + +The following would throw an `Error`: + +[source, javascript] +---- +const User = ogm.model("NotFound"); +---- diff --git a/docs/asciidoc/ogm/methods/count.adoc b/docs/asciidoc/ogm/methods/count.adoc deleted file mode 100644 index 6211806609..0000000000 --- a/docs/asciidoc/ogm/methods/count.adoc +++ /dev/null @@ -1,20 +0,0 @@ -[[ogm-methods-count]] -= Count - -Use to count nodes. - -== Usage - -=== Basic - -[source, javascript] ----- -const User = ogm.model("User"); - -const usersCount = await User.count(); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. diff --git a/docs/asciidoc/ogm/methods/create.adoc b/docs/asciidoc/ogm/methods/create.adoc deleted file mode 100644 index 478d5aae08..0000000000 --- a/docs/asciidoc/ogm/methods/create.adoc +++ /dev/null @@ -1,30 +0,0 @@ -[[ogm-methods-create]] -= Create - -Use to create many nodes. The `model.create` method maps to the underlying <> operation. - -== Usage -[source, javascript] ----- -const Movie = ogm.model("Movie"); - -await Movie.create({ input: [{ title: "The Matrix" }] }) ----- - -== Args - -=== `input` -JavaScript object representation of the GraphQL `input` input type, used for <>. - -=== `selectionSet` - -Reference: <> - -=== `args` -The arguments to the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/delete.adoc b/docs/asciidoc/ogm/methods/delete.adoc deleted file mode 100644 index 780fc2e78b..0000000000 --- a/docs/asciidoc/ogm/methods/delete.adoc +++ /dev/null @@ -1,29 +0,0 @@ -[[ogm-methods-delete]] -= Delete - -Use to delete many nodes. The `model.delete` method maps to the underlying <> operation. - -== Usage -[source, javascript] ----- -const User = ogm.model("User"); - -await User.delete({ where: { name: "dan" }}); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. - -=== `delete` -JavaScript object representation of the GraphQL `delete` input type, used for <>. - -=== `args` -The arguments for the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/find.adoc b/docs/asciidoc/ogm/methods/find.adoc deleted file mode 100644 index 5647dc969a..0000000000 --- a/docs/asciidoc/ogm/methods/find.adoc +++ /dev/null @@ -1,56 +0,0 @@ -[[ogm-methods-find]] -= Find - -Use to update many nodes. The `model.find` method maps to the underlying schema <>. - -== Usage - -=== Basic - -[source, javascript] ----- -const User = ogm.model("User"); - -const users = await User.find(); ----- - -== Relationships -Reference: <> - -[source, javascript] ----- -const User = ogm.model("User"); - -const selectionSet = ` - { - posts { - content - } - } -`; - -const users = await User.find({ - selectionSet, -}); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. - -=== `options` -JavaScript object representation of the GraphQL `options` input type, used for <>. - -=== `selectionSet` - -Reference: <> - -=== `args` -The arguments for the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/index.adoc b/docs/asciidoc/ogm/methods/index.adoc deleted file mode 100644 index 42a02f1bdf..0000000000 --- a/docs/asciidoc/ogm/methods/index.adoc +++ /dev/null @@ -1,12 +0,0 @@ -[[ogm-methods]] -= Methods - -You can call the following on a model; - -. <> -. <> -. <> -. <> -. <> - -Each method maps to the underlying generated Query or Mutation for that Model. diff --git a/docs/asciidoc/ogm/methods/update.adoc b/docs/asciidoc/ogm/methods/update.adoc deleted file mode 100644 index 0efdf7275d..0000000000 --- a/docs/asciidoc/ogm/methods/update.adoc +++ /dev/null @@ -1,45 +0,0 @@ -[[ogm-methods-update]] -= Update - -Use to update many nodes. The `model.update` method maps to the underlying <> operation. - -== Usage -[source, javascript] ----- -const User = ogm.model("User"); - -const { users } = await User.update({ - where: { name: "bob" }, - update: { name: "Bob" }, -}); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. - -=== `update` -JavaScript object representation of the GraphQL `update` input type, used for <>. - -=== `connect` -JavaScript object representation of the GraphQL `connect` input type, used for <>. - -=== `disconnect` -JavaScript object representation of the GraphQL `disconnect` input type, used for <>. - -=== `create` -JavaScript object representation of the GraphQL `create` input type, used for <>. - -=== `selectionSet` - -Reference: <> - -=== `args` -The arguments for the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/troubleshooting/index.adoc b/docs/asciidoc/troubleshooting/index.adoc index 7a3c5819e2..82d88b2c77 100644 --- a/docs/asciidoc/troubleshooting/index.adoc +++ b/docs/asciidoc/troubleshooting/index.adoc @@ -19,6 +19,7 @@ Alternatively, if you are debugging a particular functionality, you can specify 3. `@neo4j/graphql:graphql` - Logs the GraphQL query and variables 4. `@neo4j/graphql:execute` - Logs the Cypher and Cypher paramaters before execution, and summary of execution +[[troubleshooting-query-tuning]] == Query Tuning Hopefully you won't need to perform any query tuning, but if you do, the Neo4j GraphQL Library allows you to set the full array of query options on construction of the library. diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 36cf42539b..c84b5f4c07 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -119,13 +119,21 @@ + + + + + + + + @@ -135,32 +143,38 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + From 29f758d61d0b707d768bb3c8b20f06df49c4efe8 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Fri, 30 Jul 2021 11:52:30 +0100 Subject: [PATCH 20/20] Changes reflecting @danstarns comments --- docs/asciidoc/auth/authorization/allow.adoc | 6 +- docs/asciidoc/auth/authorization/bind.adoc | 4 +- docs/asciidoc/driver-configuration.adoc | 74 ++++++++++++++++++- docs/asciidoc/mutations/index.adoc | 16 ++-- docs/asciidoc/mutations/update.adoc | 2 +- .../ogm/examples/custom-resolvers.adoc | 1 + 6 files changed, 87 insertions(+), 16 deletions(-) diff --git a/docs/asciidoc/auth/authorization/allow.adoc b/docs/asciidoc/auth/authorization/allow.adoc index d16eee76cc..9fe3be3180 100644 --- a/docs/asciidoc/auth/authorization/allow.adoc +++ b/docs/asciidoc/auth/authorization/allow.adoc @@ -1,7 +1,7 @@ [[auth-authorization-allow]] = Allow -Use `allow` to ensure that on matched nodes, there is a match between a value on the JWT and a property on each matched node. Taking a closer look, let's create two users in a hypothetical empty database: +Use `allow` to ensure that on matched nodes, there is equality between a value on the JWT and a property on each matched node. Taking a closer look, let's create two users in a hypothetical empty database: [source, cypher] ---- @@ -9,7 +9,7 @@ CREATE (:User { id: "user1", name: "one" }) CREATE (:User { id: "user2", name: "two" }) ---- -For the node type above, we might have the following simple type definition: +For the label and properties of the nodes created above, we would have the equivalent GraphQL type definition as follows: [source, graphql] ---- @@ -19,7 +19,7 @@ type User { } ---- -Now we have two users in our database, and a simple type definition - we need to restrict `user1` from accessing `user2`? This is where `allow` comes in: +Now that we have two users in our database, and a simple type definition - let's say we need to restrict `user1` from accessing `user2`. This is where `allow` comes in: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/bind.adoc b/docs/asciidoc/auth/authorization/bind.adoc index d0c94027f9..8590372660 100644 --- a/docs/asciidoc/auth/authorization/bind.adoc +++ b/docs/asciidoc/auth/authorization/bind.adoc @@ -1,14 +1,14 @@ [[auth-authorization-bind]] = Bind -Use bind to ensure that on creating or updating nodes, there is a match between a value on the JWT and a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, let's create a user in our database: +Use bind to ensure that on creating or updating nodes, there is equality between a value on the JWT and a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, let's create a user in our database: [source, cypher] ---- CREATE (:User { id:"user1", name: "one" }) ---- -For the node type above, we might have the following simple type definition: +For the label and properties of the node created above, we would have the equivalent GraphQL type definition as follows: [source, graphql] ---- diff --git a/docs/asciidoc/driver-configuration.adoc b/docs/asciidoc/driver-configuration.adoc index 1d22da0ac8..c1ad523374 100644 --- a/docs/asciidoc/driver-configuration.adoc +++ b/docs/asciidoc/driver-configuration.adoc @@ -132,7 +132,9 @@ await ogm.checkNeo4jCompat(); == Specifying Neo4j database -The Neo4j database may be added to the GraphQL context object: +There are two ways to specify which database within a DBMS should be used. + +=== Context [source, javascript] ---- @@ -158,9 +160,44 @@ const server = new ApolloServer({ }); ---- +=== `Neo4jGraphQL` constructor + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + driverConfig: { + database: "my-database", + }, + }, +}); + +const server = new ApolloServer({ + schema, +}); +---- + == Specifying Neo4j Bookmarks -A Neo4j driver bookmark may be added to the GraphQL context object: +There are two ways to specify which database bookmarks to use. + +=== Context [source, javascript] ---- @@ -185,3 +222,36 @@ const server = new ApolloServer({ context: { driverConfig: { bookmarks: ["my-bookmark"] } } }); ---- + +=== `Neo4jGraphQL` constructor + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + driverConfig: { + bookmarks: ["my-bookmark"], + }, + }, +}); + +const server = new ApolloServer({ + schema, +}); +---- diff --git a/docs/asciidoc/mutations/index.adoc b/docs/asciidoc/mutations/index.adoc index 5b03e916c6..c3929b36c3 100644 --- a/docs/asciidoc/mutations/index.adoc +++ b/docs/asciidoc/mutations/index.adoc @@ -7,12 +7,12 @@ Several Mutations are automatically generated for each type defined in type defi - <> - update nodes, and recursively perform any operations from there - <> - delete nodes, and recursively delete or disconnect further nodes in the graph -*A note on nested Mutations* +== A note on nested Mutations -> You will see some basic examples of nested Mutations in this chapter, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! -> -> However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. -> -> If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. -> -> If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. +You will see some basic examples of nested Mutations in this chapter, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! + +However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. + +If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. + +If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. diff --git a/docs/asciidoc/mutations/update.adoc b/docs/asciidoc/mutations/update.adoc index ac4fb8aea3..1776d86b5b 100644 --- a/docs/asciidoc/mutations/update.adoc +++ b/docs/asciidoc/mutations/update.adoc @@ -74,7 +74,7 @@ mutation { } ---- -== Nested update +== Nested create Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: diff --git a/docs/asciidoc/ogm/examples/custom-resolvers.adoc b/docs/asciidoc/ogm/examples/custom-resolvers.adoc index d2403c537a..1a38c1ca6e 100644 --- a/docs/asciidoc/ogm/examples/custom-resolvers.adoc +++ b/docs/asciidoc/ogm/examples/custom-resolvers.adoc @@ -110,6 +110,7 @@ const neoSchema = new Neo4jGraphQL({ const server = new ApolloServer({ schema: neoSchema.schema, + context: ({ req }) => ({ req }), }); server.listen().then(({ url }) => {