@cypher Directive

The @cypher directive binds a GraphQL field to the result(s) of a Cypher query.

1. 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!,
) on FIELD_DEFINITION

2. 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 we run the Cypher. 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 (").

3. Globals

Global variables are available for use within the Cypher statement.

3.1. 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.

3.2. 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
        """
    )
}

3.3. 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
    """)
}

4. Return values

The return value of the Cypher statement must be of the same type to which the directive is applied.

4.1. 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()")
}

4.2. 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.

5. Usage examples

5.1. On an object type field

Below we add a field similarMovies to our Movie, which is bound to a Cypher query, 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
            """
        )
}

5.2. On a Query type field

Below we add 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
            """
        )
}

5.3. On a Mutation type field

Below we add 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
            """
        )
}