@cypher
directive
The @cypher
directive binds a GraphQL field to the result(s) of a Cypher query.
Definition
"""Instructs @neo4j/graphql to run the specified Cypher statement in order to resolve the value of the field to which the directive is applied."""
directive @cypher(
"""The Cypher statement to run which returns a value of the same type composition as the field definition on which the directive is applied."""
statement: String!,
"""[Experimental] Name of the returned variable from the Cypher statement, if provided, the query will be optimized to improve performance."""
columnName: String
) on FIELD_DEFINITION
Character Escaping
All double quotes must be double escaped when used in a @cypher directive - once for GraphQL and once for the function in which the Cypher is wrapped. For example, at its simplest:
type Example {
string: String!
@cypher(
statement: """
RETURN \\"field-level string\\"
"""
)
}
type Query {
string: String!
@cypher(
statement: """
RETURN \\"Query-level string\\"
"""
)
}
Note the double-backslash (\\
) before each double quote ("
).
Globals
Global variables are available for use within the Cypher statement.
this
The value this
is a reference to the currently resolved node, and it can be used to traverse the graph.
This can be seen in the usage example On an object type field below.
auth
The value auth
is represented by the following TypeScript interface definition:
interface Auth {
isAuthenticated: boolean;
roles?: string[];
jwt: any;
}
For example, you could use the JWT in the request to return the value of the currently logged in User:
type User {
id: String
}
type Query {
me: User @cypher(
statement: """
MATCH (user:User {id: $auth.jwt.sub})
RETURN user
"""
)
}
cypherParams
Use to inject values into the cypher query from the GraphQL context function.
Inject into context:
const server = new ApolloServer({
typeDefs,
context: () => {
return {
cypherParams: { userId: "user-id-01" }
}
}
});
Use in cypher query:
type Query {
userPosts: [Post] @cypher(statement: """
MATCH (:User {id: $cypherParams.userId})-[:POSTED]->(p:Post)
RETURN p
""")
}
Return values
The return value of the Cypher statement must be of the same type to which the directive is applied.
Scalar values
The Cypher statement must return a value which matches the scalar type to which the directive was applied.
type Query {
randomNumber: Int @cypher(statement: "RETURN rand()")
}
Object types
When returning an object type, all fields of the type must be available in the Cypher return value. This can be achieved by either returning the entire object from the Cypher query, or returning a map of the fields which are required for the object type. Both approaches are demonstrated below:
type User {
id
}
type Query {
users: [User]
@cypher(
statement: """
MATCH (u:User)
RETURN u
"""
)
}
type User {
id
}
type Query {
users: [User] @cypher(statement: """
MATCH (u:User)
RETURN {
id: u.id
}
""")
}
The downside of the latter approach is that you will need to adjust the return object as you change your object type definition.
columnName
The columName
argument will change the translation of custom Cypher. Instead of using apoc.cypher.runFirstColumnMany it will directly wrap the query within a CALL { }
subquery. This behvaiour has proven to be much more performant for the same queries.
columnName
should be the name of the returned variable to be used in the rest of the query. For example:
The graphql query:
type query {
test: String! @cypher(statement: "MATCH(m:Movie) RETURN m", columnName: "m")
}
Would get translated to:
CALL {
MATCH(m:Movie) RETURN m
}
WITH m AS this
RETURN this
Additionally, escaping strings is no longer needed when columName
is set.
This alternative behaviour may lead to unexpected changes, mainly if using Neo4j 5.x, where subqueries need to be aliased. |
Usage examples
On an object type field
In the example below, a field similarMovies
is bound to the Movie
type, to find other movies with an overlap of actors:
type Actor {
actorId: ID!
name: String
movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}
type Movie {
movieId: ID!
title: String
description: String
year: Int
actors(limit: Int = 10): [Actor!]!
@relationship(type: "ACTED_IN", direction: IN)
similarMovies(limit: Int = 10): [Movie]
@cypher(
statement: """
MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie)
WITH rec, COUNT(*) AS score ORDER BY score DESC
RETURN rec LIMIT $limit
"""
)
}
On a Query type field
The example below demonstrates a simple Query to return all of the actors in the database:
type Actor {
actorId: ID!
name: String
}
type Query {
allActors: [Actor]
@cypher(
statement: """
MATCH (a:Actor)
RETURN a
"""
)
}
On a Mutation type field
The example below demonstrates a simple Mutation using a Cypher query to insert a single actor with the specified name argument:
type Actor {
actorId: ID!
name: String
}
type Mutation {
createActor(name: String!): Actor
@cypher(
statement: """
CREATE (a:Actor {name: $name})
RETURN a
"""
)
}
Was this page helpful?