- [Neo4j GraphQL Toolbox](#neo4j-graphql-toolbox) - [GraphQL Directives](#graphql-directives) - [Basics](#basics) - [Autogeneration](#autogeneration) - [`@id`](#id) - [`@timestamp`](#timestamp) - [`@populatedBy`](#populatedby) - [Example (slug)](#example-slug) - [Example (modifiedBy)](#example-modifiedby) - [`@cypher`](#cypher) - [`this`](#this) - [`auth`](#auth) - [`cypherParams`](#cypherparams) - [`alias`](#alias) - [Indexes and Constraints](#indexes-and-constraints) - [`@unique`](#unique) - [`@fulltext`](#fulltext) - [Schema Configuration](#schema-configuration) - [Type Configuration](#type-configuration) - [`@query`](#query) - [`@mutation`](#mutation) - [`@subscription`](#subscription) - [Field Configuration](#field-configuration) - [Query and Aggregation](#query-and-aggregation) - [Aggregation](#aggregation) - [Filtering](#filtering) - [Sorting](#sorting) - [Pagination](#pagination) - [Authentication and Authorization](#authentication-and-authorization) - [Authorization without Authentication](#authorization-without-authentication) - [Excluded Directives OGM](#excluded-directives-ogm) - [Introspector](#introspector) - [Codegen](#codegen) Neo4j supports GraphQL with NodeJS, making it possible to interact with the database like a OGM. There is the most comprehensive docs I found: https://neo4j.com/docs/graphql/current/ https://www.npmjs.com/package/@neo4j/graphql-ogm is a GraphQL OGM for Neo4j GraphQL. ## Neo4j GraphQL Toolbox https://graphql-toolbox.neo4j.io/ > The Neo4j GraphQL Toolbox is an onboarding, low-code tool that can be integrated to Neo4j. It was created for development and experimentation with Neo4j GraphQL APIs. With it, you can: 1. Connect to a Neo4j database with valid credentials. 2. Define (or introspect) the type definitions. 3. Build the Neo4j GraphQL schema. 4. Experiment, query, and play. Enter your GraphQL schema in toolbox, it will generate the full API server for experiment. You can use the generated Queries and Mutations with type prompts. No real server is required to run the generated API. Build a local graphql apollo server, with GraphQL codegen library, you can generate TypeScript GraphQL Client code. Include the operations you've experimented, then you can use the generated client code to interact with the database. ## GraphQL Directives ### Basics Direction of relationship means the direction of the arrow in the graph. `IN` is the direction the arrow in pointing to. An edge can also have properties defined in interface. ```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 @relationshipProperties { roles: [String] } ``` ### Autogeneration https://neo4j.com/docs/graphql/current/type-definitions/directives/autogeneration/ #### `@id` Autogeneration of IDs for the field. ```graphql type User { id: ID! @id username: String! } ``` #### `@timestamp` ```graphql type User { createdAt: DateTime! @timestamp(operations: [CREATE]) updatedAt: DateTime! @timestamp(operations: [UPDATE]) lastModified: DateTime! @timestamp # OR (these 2 are equivalent) lastModified: DateTime! @timestamp(operations: [CREATE, UPDATE]) } ``` #### `@populatedBy` specify a callback function that gets executed during GraphQL query parsing, to populate fields which have not been provided within the input. ##### Example (slug) ```graphql type Product { name: String! slug: String! @populatedBy(callback: "slug", operations: [CREATE, UPDATE]) } ``` Pass callback function from client side. ```js const slugCallback = async (root) => { return `${root.name}_slug`; }; new Neo4jGraphQL({ typeDefs, driver, features: { populatedBy: { callbacks: { slug: slugCallback, }, }, }, }); ``` ##### Example (modifiedBy) Context values can be used in callback function. ```graphql type Record { content: String! modifiedBy: @populatedBy(callback: "modifiedBy", operations: [CREATE, UPDATE]) } ``` ```js const modifiedByCallback = async (_parent, _args, context) => { return context.username; }; new Neo4jGraphQL({ typeDefs, driver, features: { populatedBy: { callbacks: { modifiedBy: modifiedByCallback, }, }, }, }); ``` ### `@cypher` https://neo4j.com/docs/graphql/current/type-definitions/directives/cypher/ #### `this` This value is a reference to the currently resolved node, and it can be used to traverse the graph. ```graphql query { Movie { title actors: ACTED_IN @this { role actor { name } } directors: DIRECTED @this { director { name } } } } ``` #### `auth` This value is represented by the following TypeScript interface definition: ```ts interface Auth { isAuthenticated: boolean; roles?: string[]; jwt: any; } ``` You can use the JWT in the request to return the value of the currently logged in User: ```graphql type User { id: String } type Query { me: User @cypher( statement: """ MATCH (user:User {id: $jwt.sub}) RETURN user """ columnName: "user" ) } ``` #### `cypherParams` Inject values into cypher query from GraphQL context function. Can be used to parse JWT token and inject user id into context. ```js const server = new ApolloServer({ typeDefs, }); await startStandaloneServer(server, { context: async ({ req }) => { const userId = parseJwt(req.headers.authorization); return { cypherParams: { userId: userId } }; }, }); ``` ```graphql type Query { userPosts: [Post] @cypher( statement: """ MATCH (:User {id: $userId})-[:POSTED]->(p:Post) RETURN p """ columnName: "p" ) } ``` #### `alias` maps a GraphQL field to a Neo4j property on a node or relationship ```graphql type User { id: ID! @id @alias(property: "dbId") username: String! } ``` ### Indexes and Constraints https://neo4j.com/docs/graphql/current/type-definitions/directives/indexes-and-constraints/ #### `@unique` ```graphql type Colour { hexadecimal: String! @unique } type Colour { hexadecimal: String! @unique(constraintName: "unique_colour") } ``` #### `@fulltext` Use `@fulltext` directive to add a Full text index. ```graphql type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) { name: String! color: Color! @relationship(type: "OF_COLOR", direction: OUT) } ``` This `@fulltext` directive will create a full text index on the `name` field of the `Product` type. ```sql CREATE FULLTEXT INDEX ProductName FOR (n:Product) ON EACH [n.name] ``` This will generate a new query ```graphql type Query { productsFulltextProductName( phrase: String! where: ProductFulltextWhere sort: [ProductFulltextSort!] limit: Int offset: Int ): [ProductFulltextResult!]! } """ The result of a fulltext search on an index of Product """ type ProductFulltextResult { score: Float product: Product } """ The input for filtering a fulltext query on an index of Product """ input ProductFulltextWhere { score: FloatWhere product: ProductWhere } """ The input for sorting a fulltext query on an index of Product """ input ProductFulltextSort { score: SortDirection product: ProductSort } """ The input for filtering the score of a fulltext search """ input FloatWhere { min: Float max: Float } ``` **Sample Usage** ```graphql query { productsFulltextProductName( phrase: "Hot sauce" where: { score: { min: 1.1 } } sort: [{ product: { name: ASC } }] ) { score product { name } } } ``` ### Schema Configuration #### Type Configuration https://neo4j.com/docs/graphql/current/schema-configuration/type-configuration/ ##### `@query` This directive is used to limit the availability of query operations in the library. ```graphql directive @query( read: Boolean! = true aggregate: Boolean! = false ) on OBJECT | SCHEMA ``` ```graphql type Movie @query(read: false, aggregate: true) { title: String length: Int } ``` ##### `@mutation` > This directive is used to limit the availability of mutation operations in the library. ```graphql enum MutationFields { CREATE UPDATE DELETE } directive @mutation( operations: [MutationFields!]! = [CREATE, UPDATE, DELETE] ) on OBJECT | SCHEMA # Enable only CREATE mutation type Movie @mutation(operations: [CREATE]) { title: String length: Int } ``` ##### `@subscription` > ```graphql enum SubscriptionFields { CREATE UPDATE DELETE CREATE_RELATIONSHIP DELETE_RELATIONSHIP } directive @subscription( operations: [SubscriptionFields!]! = [ CREATE UPDATE DELETE CREATE_RELATIONSHIP DELETE_RELATIONSHIP ] ) on OBJECT | SCHEMA # Enable only movieCreated subscription for Movie type Movie @subscription(operations: [CREATE]) { title: String length: Int } ``` #### Field Configuration https://neo4j.com/docs/graphql/current/schema-configuration/field-configuration/#_selectable - `@relationship` - `@selectable` - This directive sets the availability of fields on queries and aggregations. It has two arguments: - `onRead`: if disabled, this field is not available on queries and subscriptions. - `onAggregate`: if disabled, aggregations is not available for this field. - `@settable` - This directive sets the availability of the input field on creation and update mutations. It has two arguments: - `onCreate`: if disabled, this field is not available on creation operations. - `onUpdate`: if disabled, this field is not available on update operations. - `@filterable` - This directive defines the filters generated for the field to which this directive is applied. It has two arguments: - `byValue`: if disabled, this field does not generate value filters. - `byAggregate`: if disabled, this field does not generate aggregation filters. ## Query and Aggregation ### Aggregation https://neo4j.com/docs/graphql/current/queries-aggregations/aggregations/ - `shortest`, `longest` - `min`, `max`, `average`, `sum` - `min`, `max` ## Filtering - `_LT` - `_LTE` - `_GT` - `_GTE` ```graphql query { users(where: { age: { _lt: 50 } }) { id name age } } ``` These features can be disabled or enabled ```js const features = { filters: { String: { LT: true, GT: true, LTE: true, GTE: true, }, }, }; const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ``` There are much more aggregate functions. ## Sorting https://neo4j.com/docs/graphql/current/queries-aggregations/sorting/ ```graphql query { movies(options: { sort: [{ runtime: ASC }] }) { title runtime } } ``` ## Pagination https://neo4j.com/docs/graphql/current/queries-aggregations/pagination/ ```graphql query { users(options: { offset: 10, limit: 10 }) { name } } ``` ## Authentication and Authorization https://neo4j.com/docs/graphql/current/authentication-and-authorization/ `@authentication` and `@authorization` directive can be used. ```graphql type User @authentication(operations: [DELETE], jwt: { roles_INCLUDES: "admin" }) { id: ID! name: String! password: String! } ``` ### Authorization without Authentication ```graphql type User { id: ID! } type Post @authorization( filter: [ { where: { node: { author: { id: "$jwt.sub" } } } } { requireAuthentication: false operations: [READ] where: { node: { public: true } } } ] ) { title: String! content: String! public: Boolean! author: User! @relationship(type: "AUTHORED", direction: IN) } ``` ## Excluded Directives OGM https://neo4j.com/docs/graphql/current/ogm/directives/#_excluded_directives The following directives are excluded from the OGM. Reason: OGM is not designed to be exposed API which needs security measures. - `@authentication` - `@authorization` - `@subscriptionsAuthorization` - `@query` - `@mutation` - `@subscription` - `@filterable` - `@selectable` - `@settable` This doesn't mean you can't use GraphQL for exposed API. OMG is designed for internal use. This is how ogm work. Like Prisma. It's always used programmatically on server so there is no need for Auth. ```ts const ogm = new OGM({ typeDefs, driver }); const User = ogm.model("User"); const users = await User.find({ where: { name_REGEX: regex }, options: { offset, limit, sort, }, }); ``` https://neo4j.com/docs/graphql/current/ogm/installation/ ## Introspector https://github.com/neo4j/graphql/tree/dev/packages/introspector > This is a tool that enables you, with very little effort, to introspect the schema/data model in an existing Neo4j database and build up a set of data structures that can be transformed into any output format. Basically it will generate the schema from database relationships. This could be helpful when you have a database and you want to generate the schema from it, but may not help when you need to design the schema first. ## Codegen In order to use codegen (https://the-guild.dev/graphql/codegen) to generate TypeScript types and sdk, you have to run a graphql server and pass the url to schema. ```ts import type { CodegenConfig } from "@graphql-codegen/cli"; const config: CodegenConfig = { overwrite: true, schema: "http://localhost:3000/graphql", documents: "operations/**/*.gql", generates: { "src/gql/": { preset: "client", plugins: [], }, "src/gql/req.ts": { plugins: [ "typescript", "typescript-operations", "typescript-graphql-request", ], config: { rawRequest: true, }, }, "./graphql.schema.json": { plugins: ["introspection"], }, }, }; export default config; ```