diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index fa3e1352..ee37d2f9 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -61,14 +61,6 @@ * xref:introspector.adoc[Introspector] -* xref:ogm/index.adoc[] -** xref:ogm/installation.adoc[] -** xref:ogm/directives.adoc[] -** xref:ogm/selection-set.adoc[] -** xref:ogm/type-generation.adoc[] -** xref:ogm/subscriptions.adoc[] -** xref:ogm/reference.adoc[] - * *Frameworks and integrations* * xref:integrations/apollo-federation.adoc[] diff --git a/modules/ROOT/pages/directives/custom-logic.adoc b/modules/ROOT/pages/directives/custom-logic.adoc index bd79b0ea..88dcb4ae 100644 --- a/modules/ROOT/pages/directives/custom-logic.adoc +++ b/modules/ROOT/pages/directives/custom-logic.adoc @@ -1,5 +1,5 @@ = Custom logic -:page-aliases: type-definitions/cypher.adoc, type-definitions/default-values.adoc, ogm/examples/custom-resolvers.adoc, custom-resolvers.adoc +:page-aliases: type-definitions/cypher.adoc, type-definitions/default-values.adoc, custom-resolvers.adoc :description: This page describes how to use directives for custom logic. == `@cypher` diff --git a/modules/ROOT/pages/directives/index.adoc b/modules/ROOT/pages/directives/index.adoc index 0e99b929..598e9518 100644 --- a/modules/ROOT/pages/directives/index.adoc +++ b/modules/ROOT/pages/directives/index.adoc @@ -140,17 +140,6 @@ of any required fields that is passed as arguments to the custom resolver. |=== -== OGM - -[cols="2,5"] -|=== -| Directive | Description - -| xref::ogm/directives.adoc#_private[`@private`] -| Protects fields which should only be available through the xref::ogm/index.adoc[OGM]. - -|=== - == Relay [cols="2,5"] diff --git a/modules/ROOT/pages/driver-configuration.adoc b/modules/ROOT/pages/driver-configuration.adoc index 6b760192..f4c0eba0 100644 --- a/modules/ROOT/pages/driver-configuration.adoc +++ b/modules/ROOT/pages/driver-configuration.adoc @@ -6,7 +6,7 @@ This page describes the configuration of the Neo4j GraphQL Library driver. == Neo4j Driver -For the Neo4j GraphQL Library to work, either an instance of the https://github.com/neo4j/neo4j-javascript-driver[Neo4j JavaScript driver] must be passed in on construction of your `Neo4jGraphQL` instance (or alternatively, `OGM`), or a driver, session or transaction passed into the `context.executionContext` per request. +For the Neo4j GraphQL Library to work, either an instance of the https://github.com/neo4j/neo4j-javascript-driver[Neo4j JavaScript driver] must be passed in on construction of your `Neo4jGraphQL` instance, or a driver, session or transaction passed into the `context.executionContext` per request. The examples in this page assume a Neo4j database running at "bolt://localhost:7687" with a username of "username" and a password of "password". @@ -143,31 +143,10 @@ await startStandaloneServer(server, { ---- -=== OGM - -[source, javascript, indent=0] ----- -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User @node { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "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. +Use the `checkNeo4jCompat` method available on a `Neo4jGraphQL` instance to ensure the specified DBMS is of the required version, and has the necessary functions and procedures available. The `checkNeo4jCompat` throws an `Error` if the DBMS is incompatible, with details of the incompatibilities. === `Neo4jGraphQL` @@ -192,28 +171,6 @@ const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); await neoSchema.checkNeo4jCompat(); ---- -=== `OGM` - -[source, javascript, indent=0] ----- -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User @node { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const ogm = new OGM({ typeDefs, driver }); -await ogm.checkNeo4jCompat(); ----- - == Specifying the Neo4j database Specify the database to be used within your DBMS via the `database` field under `sessionConfig` in the `context` that all resolvers share. diff --git a/modules/ROOT/pages/index.adoc b/modules/ROOT/pages/index.adoc index 479b7667..a812d36a 100644 --- a/modules/ROOT/pages/index.adoc +++ b/modules/ROOT/pages/index.adoc @@ -31,7 +31,6 @@ For every query and mutation that is executed against this generated schema, the - Options for xref::/directives/database-mapping.adoc[Database mapping] and value xref::/directives/autogeneration.adoc[Autogeneration]. - xref::/queries-aggregations/pagination/index.adoc[Pagination] options. - xref::/security/index.adoc[Security options] and additional xref::schema-configuration/index.adoc[Schema Configuration]. -- An xref::ogm/index.adoc[OGM] (Object Graph Mapper) for programmatic interaction with your GraphQL API. - A xref::getting-started/toolbox.adoc[Toolbox] (UI) to experiment with your Neo4j GraphQL API on Neo4j Desktop. diff --git a/modules/ROOT/pages/ogm/directives.adoc b/modules/ROOT/pages/ogm/directives.adoc deleted file mode 100644 index 6144a28f..00000000 --- a/modules/ROOT/pages/ogm/directives.adoc +++ /dev/null @@ -1,85 +0,0 @@ -[[ogm-private]] -:description: This page describes how to use directives in OGM. -:page-aliases: ogm/private.adoc -= Directives - -This page describes how to use directives in OGM and which ones were excluded. - -== `@private` - -The `@private` directive allows specifying what fields should only be accessible through the OGM. -This is a useful feature when in need of hiding fields such as passwords, for example. - -=== Definition - -[source, graphql, indent=0] ----- -"""Instructs @neo4j/graphql to only expose a field through the Neo4j GraphQL OGM.""" -directive @private on FIELD_DEFINITION ----- - -=== Usage - -Given the following type definition: - -[source, graphql, indent=0] ----- -type User @node { - username: String! - email: String! - password: String! @private -} ----- - -In your application, you may want to hash passwords and hide them from public viewing. -This can be done with a custom resolver, using the OGM, to update and set passwords. - -Such a scenario gets more apparent when you want to use the same type definitions to drive a public-facing schema and an OGM. -For example: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQL } from "@neo4j/graphql"; -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const typeDefs = ` - type User @node { - username: String! - email: String! - password: String! @private - } -`; - -// public without password -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -// private with access to password -const ogm = new OGM({ typeDefs, driver }); - -Promise.all([neoSchema.getSchema(), ogm.init()]).then(([schema]) => { - const apolloServer = new ApolloServer({ schema }); -}) ----- - -== Excluded directives - -The following directives are excluded from the OGM's schema: - -- `@authentication` -- `@authorization` -- `@subscriptionsAuthorization` -- `@query` -- `@mutation` -- `@subscription` -- `@filterable` -- `@selectable` -- `@settable` - -The reason is that the OGM is only ever used programmatically, as opposed to an exposed API which needs these security measures. -See the page on xref:directives/index.adoc[Neo4j GraphQL directives] for more information. diff --git a/modules/ROOT/pages/ogm/index.adoc b/modules/ROOT/pages/ogm/index.adoc deleted file mode 100644 index 5b5a822d..00000000 --- a/modules/ROOT/pages/ogm/index.adoc +++ /dev/null @@ -1,20 +0,0 @@ -[[ogm]] -:description: This section describes how to use the OGM functionality in Neo4j GraphQL. -= OGM - -Most applications don't only expose a single GraphQL API -- there may also be scheduled jobs, authentication, and migrations keeping an application on. -The OGM (Object Graph Mapper) can be used to programmatically interact with your Neo4j GraphQL API, so you can achieve these goals. - -This section includes: - -* xref::ogm/installation.adoc[Installation] - How to install the OGM and usage examples. -* xref::ogm/directives.adoc[Directives] - How to use directives in OGM. -* xref::ogm/selection-set.adoc[Selection set] - How to use this GraphQL functionality in OGM. -* xref::ogm/type-generation.adoc[TypeScript Type Generation] - How to generate types in Neo4j GraphQL using TypeScript. -* xref::ogm/reference.adoc[API Reference] - Reference guide to all functionalities in OGM. - -[NOTE] -==== -Before using the OGM, it's recommended that you have a good understanding of the Neo4j GraphQL Library first. -Walk through the xref::getting-started/index.adoc[Getting started] guide or take the course https://graphacademy.neo4j.com/courses/graphql-basics/?ref=docs[Introduction to Neo4j & GraphQL] in GraphAcademy for more instructions. -==== diff --git a/modules/ROOT/pages/ogm/installation.adoc b/modules/ROOT/pages/ogm/installation.adoc deleted file mode 100644 index e6c6c513..00000000 --- a/modules/ROOT/pages/ogm/installation.adoc +++ /dev/null @@ -1,316 +0,0 @@ -[[ogm-installation]] -:description: This page describes how to install the OGM in Neo4j GraphQL and how to use it. -= Installation -:page-aliases: ogm/examples/index.adoc - -You can install the OGM into a new or existing Node.js project in a similar way to how you install the Neo4j GraphQL Library. -It has the following dependencies: - -* `@neo4j/graphql-ogm`: the OGM package. -* `graphql`: the package used by the Neo4j GraphQL Library to generate a schema and execute queries and mutations. -* `neo4j-driver`: the official Neo4j Driver package for JavaScript, necessary for interacting with the database. - -[source, bash, indent=0] ----- -npm install @neo4j/graphql-ogm graphql neo4j-driver ----- - - -== Usage examples - -Here are some examples of how you can use the OGM. - -[[ogm-examples-custom-resolvers]] -=== Custom Resolvers - -The OGM has access to some fields which the Neo4j GraphQL Library doesn't. -It is common practice to use the OGM to create custom resolvers when dealing with such fields. -For example, you can have a `password` field marked with the `@private` directive and a custom resolver for creating users with passwords. - -Execute the following to create an example application directory and create a new project: - -[source, bash, indent=0] ----- -mkdir ogm-custom-resolvers-example -cd ogm-custom-resolvers-example -npm init es6 --yes -touch index.js ----- - -Install the dependencies: - -[source, bash, indent=0] ----- -npm install @neo4j/graphql-ogm graphql neo4j-driver @apollo/server ----- - -Assuming a running Neo4j database at "neo4j://localhost:7687" with username "username" and password "password", in your empty `index.js` file, add the following code: - -[source, javascript, indent=0] ----- -import { ApolloServer } from "@apollo/server"; -import { startStandaloneServer } from "@apollo/server/standalone"; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -import { createJWT, comparePassword } from "./utils.js"; // example util function, more information below - -const driver = neo4j.driver( - "neo4j://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const typeDefs = `#graphql - type User @node { - id: ID @id - username: String! - password: String! @private - } - - type Mutation { - signUp(username: String!, password: String!): String! ### JWT - signIn(username: String!, password: String!): String! ### JWT - } -`; - -const ogm = new OGM({ typeDefs, driver }); -const User = ogm.model("User"); - -const resolvers = { - Mutation: { - signUp: async (_source, { username, password }) => { - const [existing] = await User.find({ - where: { - username, - }, - }); - - if (existing) { - throw new Error(`User with username ${username} already exists!`); - } - - const { users } = await User.create({ - input: [ - { - username, - password, - } - ] - }); - - return createJWT({ sub: users[0].id }); - }, - - signIn: async (_source, { username, 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 }); - }, - }, -}; - - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - resolvers, - features: { - authorization: { - key: "secret", - }, - }, -}); - -async function main() { - await ogm.init(); - - const server = new ApolloServer({ - schema: await neoSchema.getSchema(), - }); - - const { url } = await startStandaloneServer(server, { - listen: { port: 4000 }, - context: async ({ req }) => ({ - token: req.headers.authorization, - }), - }); - - console.log(`🚀 Server ready at ${url}`); -} - -main(); ----- - -Create the file `utils.js` in the same directory. -Install additional dependencies: - -[source, bash, indent=0] ----- -npm install bcrypt jsonwebtoken ----- - -Add the following code to `utils.js`: - -[source, javascript, indent=0] ----- -import bcrypt from "bcrypt"; -import jwt from "jsonwebtoken"; - -export function createJWT(data) { - return new Promise((resolve, reject) => { - jwt.sign(data, "", (err, token) => { - if (err) { - return reject(err); - } - - return resolve(token); - }); - }); -} - -export function comparePassword(plainText, hash) { - return new Promise((resolve, reject) => { - bcrypt.compare(plainText, hash, (err, result) => { - if (err) { - return reject(err); - } - - return resolve(result); - }); - }); -} ----- - -[NOTE] -==== -The code for the util functions `createJWT` and `comparePassword` is an example. -Adjust it to suit your use case. -==== - -Back on the command line, run the following command to start your server: - -[source, bash, indent=0] ----- -node index.js ----- - -You should see the following output: - -[source, bash, indent=0] ----- -🚀 Server ready at http://localhost:4000/ ----- - -You can execute the `signUp` mutation against the GraphQL API to sign up, but if you try querying the user through the same API, the password field will not be available. - -[[ogm-examples-rest-api]] -=== REST API - -This example demonstrates how you can use the OGM without exposing a Neo4j GraphQL API endpoint. -It starts an https://expressjs.com/[Express] server and uses the OGM to interact with the Neo4j GraphQL Library, exposed via a REST endpoint. - -Execute the following to create an example application directory and a new project: - -[source, bash, indent=0] ----- -mkdir ogm-rest-example -cd ogm-rest-example -npm init es6 --yes -touch index.js ----- - -Install the dependencies: - -[source, bash, indent=0] ----- -npm install @neo4j/graphql-ogm graphql neo4j-driver express ----- - -Assuming a running Neo4j database at "neo4j://localhost:7687" with username "username" and password "password", in your empty `index.js` file, add the following code: - -[source, javascript, indent=0] ----- -import express from "express"; -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const typeDefs = ` - type User @node { - id: ID - name: String - } -`; - -const ogm = new OGM({ - typeDefs, - driver, - features: { filters: { String: { MATCHES: true } } }, -}); - -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_MATCHES: regex }, - options: { - offset, - limit, - sort, - }, - }); - - return res.json(users).end(); -}); - -const port = 4000; - -ogm.init().then(() => { - app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}/users`); - }); -}); ----- - -In your application directory, you can run this application: - -[source, bash, indent=0] ----- -node index.js ----- - -You should see the following output: - -[source, bash, indent=0] ----- -Example app listening at http://localhost:4000/users ----- - -The REST API should now be available at `http://localhost:4000`, with a single working route `/users`. diff --git a/modules/ROOT/pages/ogm/reference.adoc b/modules/ROOT/pages/ogm/reference.adoc deleted file mode 100644 index 0ded4793..00000000 --- a/modules/ROOT/pages/ogm/reference.adoc +++ /dev/null @@ -1,462 +0,0 @@ -[[ogm-api-reference]] -:description: This page is a reference guide to all functionalities in OGM. -= API Reference -:page-aliases: ogm/api-reference/model/aggregate.adoc, ogm/api-reference/model/create.adoc, \ -ogm/api-reference/model/find.adoc, ogm/api-reference/model/delete.adoc, \ -ogm/api-reference/model/update.adoc, ogm/api-reference/model/index.adoc, \ -ogm/api-reference/index.adoc, ogm/api-reference/ogm.adoc, ogm/api-reference/type-generation.adoc - -The following sections work as a reference guide to all functionalities in OGM and in the Model class. - -== `OGM` - -[cols="1,2,2"] -|=== -|Function|Description|Example - -|`constructor` -|Returns an `OGM` instance. -Takes an `input` object as a parameter, which is then passed to the `Neo4jGraphQL` constructor. -a| -[source, javascript, indent=0] ----- -const ogm = new OGM({ - typeDefs, -}); ----- - -|`init` -|Asynchronous method to initialize the OGM. -Internally, calls `Neo4jGraphQL.getSchema()` to generate a GraphQL schema, and stores the result. -Initializes any models which have been created before this execution, and will throw an error if any of them are invalid. -a| -[source, javascript, indent=0] ----- -await ogm.init(); ----- - -|`model` -|Returns a `model` instance matching the passed in name, or (if the OGM has been initialized) throws an `Error` if one can't be found. -Accepts a single argument `name` of type `string`. -a| -.Type definition -[source, graphql, indent=0] ----- -type User @node { - username: String! -} ----- - -.Query return -[source, javascript, indent=0] ----- -const User = ogm.model("User"); ----- - -.Wrong query -[source, javascript, indent=0] ----- -const User = ogm.model("NotFound"); ----- - -|`generate` -|Either writes to specified `outFile` or returns a string - if `noWrite` is set. -a| -.Writing to outFile -[source, typescript, indent=0] ----- -import { OGM, generate } from "@neo4j/graphql-ogm"; - -const typeDefs = ` - type Movie @node { - id: ID - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const ogm = new OGM({ typeDefs, driver }); - -await generate({ - ogm, - outFile: "path/to/my/file.ts", -}); - -console.log("Types Generated"); ----- - -.Writing with noWrite -[source, typescript, indent=0] ----- -import { OGM, generate } from "@neo4j/graphql-ogm"; - -const typeDefs = ` - type Movie @node { - id: ID - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const ogm = new OGM({ typeDefs, driver }); - -const source = await generate({ - ogm, - noWrite: true, -}); - -console.log("Types Generated ", source); ----- -|=== - -[discrete] -=== `assertIndexesAndConstraints` - -Asynchronous method to assert the existence of database constraints, that either resolves to `void` in a successful scenario, or throws an error if the necessary constraints do not exist following its execution. -It takes an `input` object as a parameter, being the supported fields the following examples. - -Given the type definitions saved to the variable `typeDefs` and a valid driver instance saved to the variable `driver`: - -[source, graphql, indent=0] ----- -type Book @node { - isbn: String! @unique -} ----- - -And the construction and initialization of an `OGM`, using: - -[source, javascript, indent=0] ----- -const ogm = new OGM({ - typeDefs, -}); -await ogm.init(); ----- - -The following checks whether a unique node property constraint exists for label "Book" and property "isbn", and throws an error if it does not: - -[source, javascript, indent=0] ----- -await ogm.assertIndexesAndConstraints(); ----- - -The next example creates the constraint if it does not exist: - -[source, javascript, indent=0] ----- -await ogm.assertIndexesAndConstraints({ options: { create: true } }); ----- - -[discrete] -==== Input - -Accepts the argument: - -[cols="1,2,3"] -|=== -|Name |Type |Description - -|`options` -|`AssertConstraintsOptions` -|Options for the execution of `assertIndexesAndConstraints`. -|=== - - -[discrete] -==== `AssertConstraintsOptions` - -[cols="1,2,3"] -|=== -|Name |Type |Description - -|`create` -|`boolean` -|Whether or not to create constraints if they do not yet exist. Disabled by default. -|=== - - -[[ogm-model]] -== Model - -[[ogm-api-reference-model-aggregate]] -=== `aggregate` - -This method can be used to aggregate nodes, and maps to the underlying schema xref::queries-aggregations/aggregations.adoc#_aggregate_related_nodes[Aggregate]. - -==== Arguments - -[cols="1,2,2"] -|=== -|Name|Type|Description - -|`where` -|`GraphQLWhereArg` -|A JavaScript object representation of the GraphQL `where` input type used for xref::queries-aggregations/filtering.adoc[Filtering]. -|=== - -==== Example - -Here is how you can write a query to find the longest User name: - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); - -const usersAggregate = await User.aggregate({ - aggregate: { - name: { - longest: true - } - } -}); ----- - -And this one is to find the longest User name where name starts with the letter "D": - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); - -const usersAggregate = await User.aggregate({ - where: { - name_STARTS_WITH: "D" - }, - aggregate: { - name: { - longest: true - } - } -}); ----- - - -[[ogm-api-reference-model-create]] -=== `create` - -This method can be used to update nodes, and maps to the underlying xref::mutations/create.adoc[`create`] Mutation. -It returns a `Promise` that resolves to the equivalent of the Mutation response for this operation. - -==== Arguments - -[cols="1,2,2"] -|=== -|Name|Type |Description - -|`input` -|`any` -|JavaScript object representation of the GraphQL `input` input type used for xref::mutations/create.adoc[Create] mutations. - -|`selectionSet` -|`string` or `DocumentNode` or `SelectionSetNode` -|Selection set for the Mutation, see xref::ogm/selection-set.adoc[Selection Set] for more information. - -|`args` -|`any` -|The `args` value for the GraphQL Mutation. - -|`context` -|`any` -|The `context` value for the GraphQL Mutation. - -|`rootValue` -|`any` -|The `rootValue` value for the GraphQL Mutation. -|=== - -==== Example - -Here is an example on how to create a Movie with title "The Matrix": - -[source, javascript, indent=0] ----- -const Movie = ogm.model("Movie"); - -await Movie.create({ input: [{ title: "The Matrix" }] }) ----- - -[[ogm-api-reference-model-delete]] -=== `delete` - -This method can be used to delete nodes, and maps to the underlying xref::mutations/delete.adoc[Delete] Mutation. -It returns a `Promise` which resolves to a `DeleteInfo` object: - -[cols="1,1,1"] -|=== -|Name|Type |Description - -|`nodesDeleted` -|`number` -|The number of nodes deleted. - -|`relationshipsDeleted` -|`number` -|The number of relationships deleted. -|=== - -==== Arguments - -[cols="1,2,2"] -|=== -|Name|Type |Description - -|`where` -|`GraphQLWhereArg` -|A JavaScript object representation of the GraphQL `where` input type used for xref::queries-aggregations/filtering.adoc[Filtering]. - -|`delete` -|`string` or `DocumentNode` or `SelectionSetNode` -|A JavaScript object representation of the GraphQL `delete` input type used for xref::mutations/delete.adoc[`delete`] mutations. - -|`context` -|`any` -|The `context` value for the GraphQL mutation. - -|`rootValue` -|`any` -|The `rootValue` value for the GraphQL mutation. -|=== - -==== Example - -This is how you can delete all User nodes where the name is "Dan": - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); - -await User.delete({ where: { name: "Dan" }}); ----- - -[[ogm-api-reference-model-find]] -=== `find` - -This method can be used to find nodes, and maps to the underlying schema xref::queries-aggregations/queries.adoc[Queries]. -It returns a `Promise` which resolves to an array of objects matching the type of the Model. - -==== Arguments - -[cols="1,2,2"] -|=== -|Name|Type |Description - -|`where` -|`GraphQLWhereArg` -|A JavaScript object representation of the GraphQL `where` input type used for xref::queries-aggregations/filtering.adoc[Filtering]. - -|`options` -|`GraphQLOptionsArg` -|A JavaScript object representation of the GraphQL `options` input type used for xref::queries-aggregations/sorting.adoc[Sorting] and xref::/queries-aggregations/pagination/index.adoc[Pagination]. - -|`selectionSet` -|`string` or `DocumentNode` or `SelectionSetNode` -|Selection set for the Mutation, see xref::ogm/selection-set.adoc[Selection Set] for more information. - -|`args` -|`any` -|The `args` value for the GraphQL Mutation. - -|`context` -|`any` -|The `context` value for the GraphQL Mutation. - -|`rootValue` -|`any` -|The `rootValue` value for the GraphQL Mutation. -|=== - -==== Example - -Here is how to find all user nodes in the database: - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); - -const users = await User.find(); ----- - -In case you want to find users with name "Jane Smith", here is how to do it: - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); - -const users = await User.find({ where: { name: "Jane Smith" }}); ----- - -[[ogm-api-reference-model-update]] -=== `update` - -This method can be used to update nodes, and maps to the underlying xref::mutations/update.adoc[`update`] mutation. -It returns a `Promise` that resolves to the equivalent of the mutation response for this operation. - - -==== Arguments - -[cols="1,2,2"] -|=== -|Name|Type |Description - -|`where` -|`GraphQLWhereArg` -|A JavaScript object representation of the GraphQL `where` input type used for xref::queries-aggregations/filtering.adoc[Filtering]. - -|`update` -|`any` -|A JavaScript object representation of the GraphQL `update` input type used for xref::mutations/update.adoc[`update`] Mutations. - -|`connect` -|`any` -|A JavaScript object representation of the GraphQL `connect` input type used for xref::mutations/update.adoc[`update`] Mutations. - -|`disconnect` -|`any` -|A JavaScript object representation of the GraphQL `disconnect` input type used for xref::mutations/update.adoc[`update`] Mutations. - -|`create` -|`any` -|A JavaScript object representation of the GraphQL `create` input type used for xref::mutations/update.adoc[`update`] Mutations. - -|`options` -|`GraphQLOptionsArg` -|A JavaScript object representation of the GraphQL `options` input type used for xref::queries-aggregations/sorting.adoc[Sorting] and xref::/queries-aggregations/pagination/index.adoc[Pagination]. - -|`selectionSet` -|`string` or `DocumentNode` or `SelectionSetNode` -|Selection set for the Mutation, see xref::ogm/selection-set.adoc[Selection set] for more information. - -|`args` -|`any` -|The `args` value for the GraphQL Mutation. - -|`context` -|`any` -|The `context` value for the GraphQL Mutation. - -|`rootValue` -|`any` -|The `rootValue` value for the GraphQL Mutation. -|=== - -==== Example - -Here is how to update the User with name "John" to be "Jane": - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); - -const { users } = await User.update({ - where: { name: "John" }, - update: { name: "Jane" }, -}); ----- diff --git a/modules/ROOT/pages/ogm/selection-set.adoc b/modules/ROOT/pages/ogm/selection-set.adoc deleted file mode 100644 index cade6ed2..00000000 --- a/modules/ROOT/pages/ogm/selection-set.adoc +++ /dev/null @@ -1,177 +0,0 @@ -[[ogm-selection-set]] -:description: This page describes how to use the GraphQL functionality selection set in OGM. -= Selection set - -On its own, selection set is a GraphQL specific term. -For example, when you execute a query, you have the operation: - -[source, graphql, indent=0] ----- -query { - myOperation -} ----- - -And you also have a selection set, such as the following: - -[source, graphql, indent=0] ----- -query { - myOperation { - field1 - field2 - } -} ----- - -In this case, this snippet is the selection set: - -[source, graphql, indent=0] ----- -{ - field1 - field2 -} ----- - -When using the OGM, you do not 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. -This is achieved by automatically generated basic selection sets. - -Given the following type definition: - -[source, graphql, indent=0] ----- -type Movie @node { - id: ID - name: String - genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT) - customCypher: String! @cypher(statement: "RETURN someCustomData") -} - -type Genre @node { - name: String -} ----- - -Neither relationship fields nor custom Cypher fields are included 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, indent=0] ----- -{ - id - name -} ----- - -This means that, by default, when querying for node(s), you 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 in the following section. - -== Selection set at execution time - -Using this approach, you can pass in a selection set every time you interact with the OGM. -This is an appropriate approach if the selection set is going to be different every time you ask for data. - -Here is an example: - -[source, javascript, indent=0] ----- -const { OGM } = require("@neo4j/graphql-ogm") -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const typeDefs = ` - type Movie @node { - id: ID - name: String - genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT) - customCypher: String! @cypher(statement: "RETURN someCustomData") - } - - type Genre @node { - name: String - } -`; - -const ogm = new OGM({ typeDefs, driver }); -const Movie = ogm.model("Movie"); - -const selectionSet = ` - { - id - name - genres { - name - } - customCypher - } -`; - -ogm.init().then(() => { - Movie.find({ selectionSet }).then((movies) => { - // work with movies - }) -}); ----- - -Note that the argument `selectionSet` is passed every invocation of the `Movie.find()` function. - -== Setting a default selection set - -Using this approach, you can assign a selection set to a particular Model, so that whenever it is queried, it always returns those fields by default. -This is useful if the generated selection set doesn't give enough data, but you don't need the selection set to be dynamic on each request. - -Here is an example of this: - -[source, javascript, indent=0] ----- -const { OGM } = require("@neo4j/graphql-ogm") -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const typeDefs = ` - type Movie @node { - id: ID - name: String - genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT) - customCypher: String! @cypher(statement: "RETURN someCustomData") - } - - type Genre @node { - name: String - } -`; - -const ogm = new OGM({ typeDefs, driver }); -const Movie = ogm.model("Movie"); - -const selectionSet = ` - { - id - name - genres { - name - } - customCypher - } -`; - -Movie.selectionSet = selectionSet; - -ogm.init().then(() => { - Movie.find().then((movies) => { - // work with movies - }) -}); ----- - -Note that despite not passing this selection set into `Movie.find()`, the requested fields return on each request. diff --git a/modules/ROOT/pages/ogm/subscriptions.adoc b/modules/ROOT/pages/ogm/subscriptions.adoc deleted file mode 100644 index 7c3120d0..00000000 --- a/modules/ROOT/pages/ogm/subscriptions.adoc +++ /dev/null @@ -1,255 +0,0 @@ -[[ogm-subscriptions]] -:description: This how-to guide shows how to use the OGM with subscriptions. -= How to use the OGM with subscriptions - -The Neo4j GraphQL Library can be used in conjunction with the OGM in order to extend the library's functionalities, or to take advantage of the xref:ogm/private.adoc[`@private` directive]. - -== Usage - -This section shows how to use subscriptions with the OGM inside custom resolvers. - -=== Prerequisites - -Use the following type definitions: -[source, javascript, indent=0] ----- -const typeDefs = `#graphql - type User @node { - email: String! - password: String! @private - } -`; ----- - -Set up a server that supports subscriptions. See more instructions in the xref:subscriptions/getting-started.adoc#_setting_up_an_apolloserver_server[Getting started] page. - -=== Adding the OGM - -Enable the subscriptions feature in the OGM constructor: - -[source, javascript, indent=0] ----- -const ogm = new OGM({ - typeDefs, - driver, - features: { - subscriptions: true - }, -}); ----- - -Create the `User` model, which utilizes the `@private` marked field on the `User` type in the type definitions. - -[source, javascript, indent=0] ----- -const User = ogm.model("User"); ----- - -Initialize the OGM instance before using it by adding the following line to the `main()` function: - -[source, javascript, indent=0] ----- -await ogm.init(); ----- - -=== Adding a custom resolver - -Custom resolvers can be used for multiple reasons such as performing data manipulation and checks, or interact with third party systems. -In this case, you only need to create a `User` node with the `password` field set. -You can do that by adding a sign-up mutation: - -[source, javascript, indent=0] ----- -const resolvers = { - Mutation: { - signUp: async (_source, { username, password }) => { - const [existing] = await User.find({ - where: { - username, - }, - }); - if (existing) { - throw new Error(`User with username ${username} already exists!`); - } - const { users } = await User.create({ - input: [ - { - username, - password, - }, - ], - }); - return createJWT({ sub: users[0].id }); - }, - }, -}; -const typeDefs = ` - type Mutation { - signUp(username: String!, password: String!): String! - } -`; - ----- - -[discrete] -=== Conclusion - -Altogether, it should look like this: - -[source, javascript, indent=0] ----- -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); -const typeDefs = ` - type User @node { - email: String! - password: String! @private - } - type Mutation { - signUp(username: String!, password: String!): String! # custom resolver - } -` -const resolvers = { - Mutation: { - signUp: async (_source, { username, password }) => { - const [existing] = await User.find({ - where: { - username, - }, - }); - if (existing) { - throw new Error(`User with username ${username} already exists!`); - } - const { users } = await User.create({ - input: [ - { - username, - password, - } - ] - }); - return createJWT({ sub: users[0].id }); - }, - }, -}; -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - resolvers, - feature: { - subscriptions: true, - }, -}); -const ogm = new OGM({ - typeDefs, - driver, - features: { - subscriptions: true - }, -}); -const User = ogm.model("User"); - -async function main() { - // initialize the OGM instance - await ogm.init(); - - // Apollo server setup with WebSockets - const app = express(); - const httpServer = createServer(app); - const wsServer = new WebSocketServer({ - server: httpServer, - path: "/graphql", - }); - - // Neo4j schema - const schema = await neoSchema.getSchema(); - - const serverCleanup = useServer( - { - schema, - context: (ctx) => { - return ctx; - }, - }, - wsServer - ); - - const server = new ApolloServer({ - schema, - plugins: [ - ApolloServerPluginDrainHttpServer({ - httpServer, - }), - { - async serverWillStart() { - return Promise.resolve({ - async drainServer() { - await serverCleanup.dispose(); - }, - }); - }, - }, - ], - }); - await server.start(); - - app.use( - "/graphql", - cors(), - bodyParser.json(), - expressMiddleware(server, { - context: async ({ req }) => ({ req }), - }) - ); - - const PORT = 4000; - httpServer.listen(PORT, () => { - console.log(`Server is now running on http://localhost:${PORT}/graphql`); - }); -} ----- - - -== Receiving the subscription events - -First, run the following subscription to receive `User` creation events: -[source, gql, indent=0] ----- -subscription { - userCreated { - createdUser { - email - } - event - } -} ----- - -Then run the sign-up mutation: -[source, gql, indent=0] ----- -mutation { - signUp(email: "jon.doe@xyz.com", password: "jondoe") { - email - password - } -} ----- - -The results should look like this: -[source, gql, indent=0] ----- -{ - "data": { - "userCreated": { - "createdUser": { - "email": "jon.doe@xyz.com", - "password": "jondoe" - }, - "event": "CREATE" - } - } -} ----- diff --git a/modules/ROOT/pages/ogm/type-generation.adoc b/modules/ROOT/pages/ogm/type-generation.adoc deleted file mode 100644 index b02d745f..00000000 --- a/modules/ROOT/pages/ogm/type-generation.adoc +++ /dev/null @@ -1,62 +0,0 @@ -[[type-generation]] -:description: This page describes how to generate types in Neo4j GraphQL using TypeScript. -= Type generation - -When using the `.model()` method, you receive a generic instance of the Model class. -However, due to the fact that each model's return values and args are dependant on what is in the schema, it is not possible to know ahead of time what each type is. -Instead, you can use the `generate()` function exposed from the `@neo4j/graphql` package to generate the TypeScript types for your models each time you make a schema change. - -The following example shows how to import the `generate` function from `@neo4j/graphql` and add a conditional branch to check if the `process.env.GENERATE` variable has been set like this: - -[source, bash, indent=0] ----- -GENERATE="true" ts-node index.ts ----- - -Then, once you run this with the variable set, the types become available and you can import and use them as a generic. -Here is an example: - -[source, typescript, indent=0] ----- -import { OGM, generate } from "@neo4j/graphql-ogm"; -import { ModelMap } from "./ogm-types"; // this file will be auto-generated using 'generate' -import * as neo4j from "neo4j-driver" -import * as path from "path" - -const typeDefs = ` - type Movie @node { - id: ID - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -// Generic is applied on the OGM -const ogm = new OGM({ typeDefs, driver }); - -const Movie = ogm.model("Movie"); - -async function main() { - // Only generate types when you make a schema change - if (process.env.GENERATE) { - const outFile = path.join(__dirname, "ogm-types.ts"); - - await generate({ - ogm, - outFile, - }); - - console.log("Types Generated"); - - process.exit(1); - } - - // Get full autocomplete on `Movie`, including where argument properties plus the return value - const [theMatrix] = await Movie.find({ where: { name: "The Matrix" } }); -} -main() -----