This page describes how interface and union types can be used with neo4j-graphql.js.
GraphQL supports two kinds of abstract types: interfaces and unions. Interfaces are abstract types that include a set of fields that all implementing types must include. A union type indicates that a field can return one of several object types, but doesn't specify any fields that must be included in the implementing types of the union. See the GraphQL documentation to learn more about interface and union types.
Interface types are supported in neo4j-graphql.js through the use of multiple labels in Neo4j. For example, consider the following GraphQL type definitions:
interface Person {
id: ID!
name: String
}
type User implements Person {
id: ID!
name: String
screenName: String
reviews: [Review] @relation(name: "WROTE", direction: OUT)
}
type Actor implements Person {
id: ID!
name: String
movies: [Movie] @relation(name: "ACTED_IN", direction: OUT)
}
type Movie {
movieId: ID!
title: String
}
type Review {
rating: Int
created: DateTime
movie: Movie @relation(name: "REVIEWS", direction: OUT)
}
The above GraphQL type definitions would define the following property graph model using neo4j-graphql.js:
Note that the label Person
(which represents the interface type) is added to each node of a type implementing the Person
interface (User
and Actor
),
When an interface type is included in the GraphQL type definitions, the generated create mutations will add the additional label for the interface type to any nodes of an implementing type when creating data. For example consider the following mutations.
mutation {
u1: CreateUser(name: "Bob", screenName: "bobbyTables", id: "u1") {
id
}
a1: CreateActor(name: "Brad Pitt", id: "a1") {
id
}
m1: CreateMovie(title: "River Runs Through It, A", movieId: "m1") {
movieId
}
am1: AddActorMovies(from: { id: "a1" }, to: { movieId: "m1" }) {
from {
id
}
}
}
This creates the following graph in Neo4j (note the use of multiple labels):
A query field is added to the generated Query
type for each interface. For example, querying using our Person
interface.
query {
Person {
name
}
}
{
"data": {
"Person": [
{
"name": "Bob"
},
{
"name": "Brad Pitt"
}
]
}
}
The __typename
introspection field can be added to the selection set to determine the concrete type of the object.
query {
Person {
name
__typename
}
}
{
"data": {
"Person": [
{
"name": "Bob",
"__typename": "User"
},
{
"name": "Brad Pitt",
"__typename": "Actor"
}
]
}
}
Inline fragments can be used to access fields of the concrete types in the selection set.
query {
Person {
name
__typename
... on Actor {
movies {
title
}
}
... on User {
screenName
}
}
}
{
"data": {
"Person": [
{
"name": "Bob",
"__typename": "User",
"screenName": "bobbyTables"
},
{
"name": "Brad Pitt",
"__typename": "Actor",
"movies": [
{
"title": "River Runs Through It, A"
}
]
}
]
}
}
The generated filter arguments can be used for interface types. Note however that only fields in the interface definition are included in the generated filter arguments as those apply to all concrete types.
query {
Person(filter: { name_contains: "Brad" }) {
name
__typename
... on Actor {
movies {
title
}
}
... on User {
screenName
}
}
}
{
"data": {
"Person": [
{
"name": "Brad Pitt",
"__typename": "Actor",
"movies": [
{
"title": "River Runs Through It, A"
}
]
}
]
}
}
We can also use interfaces when defining relationship fields. For example:
friends: [Person] @relation(name: "FRIEND_OF", direction: OUT)
Note that using union types for relationship types is not yet supported by neo4j-graphql.js. Unions can however be used on relationship fields.
Union types are abstract types that do not specify any fields that must be included in the implementing types of the union, therefore it cannot be assumed that the concrete types of a union include any overlapping fields. Similar to interface types, in neo4j-graphql.js an additional label is added to nodes to represent the union type.
For example, consider the following GraphQL type definitions:
union SearchResult = Blog | Movie
type Blog {
blogId: ID!
created: DateTime
content: String
}
type Movie {
movieId: ID!
title: String
}
Using the generated mutations to create the following data:
mutation {
b1: CreateBlog(
blogId: "b1"
created: { year: 2020, month: 3, day: 7 }
content: "Neo4j GraphQL is the best!"
) {
blogId
}
m1: CreateMovie(movieId: "m1", title: "River Runs Through It, A") {
movieId
}
}
The above mutations create the following data in Neo4j. Note the use of multiple node labels.
A query field is added to the Query type for each union type defined in the schema.
{
SearchResult {
__typename
}
}
{
"data": {
"SearchResult": [
{
"__typename": "Blog"
},
{
"__typename": "Movie"
}
]
}
}
Inline fragments are used in the selection set to access fields of the concrete type.
{
SearchResult {
__typename
... on Blog {
created {
formatted
}
content
}
... on Movie {
title
}
}
}
{
"data": {
"SearchResult": [
{
"__typename": "Blog",
"created": {
"formatted": "2020-03-07T00:00:00Z"
},
"content": "Neo4j GraphQL is the bedst!"
},
{
"__typename": "Movie",
"title": "River Runs Through It, A"
}
]
}
}
We can also use unions with @cypher
directive fields. Unions are often useful in the context of search results, where the result object might be one of several types. In order to support this usecase full text indexes can be used to search across multiple node labels and properties.
First, let's create a full text index in Neo4j. This index will include the :Blog(content)
and :Movie(title)
properties.
CALL db.index.fulltext.createNodeIndex("searchIndex", ["Blog","Movie"],["content", "title"])
Now we can add a search
field to the Query type that searches the full text index.
type Query {
search(searchString: String!): [SearchResult] @cypher(statement:"CALL db.index.fulltext.queryNodes("searchIndex", $searchString) YIELD node RETURN node")
}
Now we can query the search
field, leveraging the full text index.
{
search(searchString: "river") {
__typename
... on Movie {
title
}
... on Blog {
created {
formatted
}
content
}
}
}
{
"data": {
"search": [
{
"__typename": "Movie",
"title": "River Runs Through It, A"
}
]
}
}
- Using Neo4j’s Full-Text Search With GraphQL -- Defining Custom Query Fields Using The Cypher GraphQL Schema Directive