Migration to 7.0.0
This is the documentation of the GraphQL Library version 7. For the long-term support (LTS) version 5, refer to GraphQL Library version 5 LTS. |
This page lists all breaking changes from the Neo4j GraphQL Library version 6.x to 7.x and how to update it.
How to update
To update your Neo4j GraphQL Library, use npm or the package manager of choice:
npm update @neo4j/graphql
Breaking changes
Here is a list of all the breaking changes from version 6.0.0 to 7.0.0.
Removed the @unique
directive
The @unique
directive is no longer supported.
It cannot always be reliably enforced, potentially leading to data inconsistencies that don’t match the schema.
Removed implicit filtering fields
Implicit equality filtering fields have been removed.
Use the dedicated eq
field instead:
Before | After |
---|---|
|
|
The implicitEqualFilters
option of excludeDeprecatedFields
has been removed.
Required @node
directive
The @node
directive is now required.
GraphQL object types without the @node
directive are no longer considered Neo4j node representations.
Queries and mutations are only generated for types with the @node
directive.
Before | After |
---|---|
|
|
Removed the deprecated options
argument
The deprecated options
argument has been removed.
Consider the following type definition:
type Movie @node {
title: String!
}
The following shows the difference for options:
Before | After |
---|---|
|
|
The deprecatedOptionsArgument
option of excludeDeprecatedFields
has been removed.
Removed the deprecated directed
argument
The deprecated directed
argument has been removed from queries.
This argument previously allowed you to specify whether relationships should be directed or undirected at query time.
Use the queryDirection
argument of the @relationship
directive instead.
The directedArgument
option of excludeDeprecatedFields
has been removed.
Changed the accepted values of the queryDirection
argument of @relationship
Following the removal of the directed
argument, the queryDirection
argument of the @relationship
directive now only accepts two possible values:
-
DIRECTED
(default) -
UNDIRECTED
The following values are no longer supported:
-
DEFAULT_DIRECTED
-
DEFAULT_UNDIRECTED
-
DIRECTED_ONLY
-
UNDIRECTED_ONLY
Removed the deprecated typename_IN
filter
The deprecated typename_IN
filter has been removed.
Use typename
instead.
Before | After |
---|---|
|
|
The typename_IN
option of excludeDeprecatedFields
has been removed.
Aggregations are no longer generated for ID
fields
ID
fields are excluded from aggregation selection sets and aggregation filters.
Removed single element relationships
Single element relationships have been removed in favor of list relationships:
Before | After |
---|---|
|
|
Single element relationships cannot be reliably enforced, leading to data inconsistent with the schema.
If the GraphQL model requires one-to-one relationships (such as in federations) these can now be created with the @cypher
directive instead:
type Movie @node {
director: Person
@cypher(
statement: """
MATCH (this)-[:ACTED_IN]->(p:Person)
RETURN p
"""
columnName: "p"
)
}
Removed the connect overwrite
argument
The overwrite
argument has been removed in connect operations.
In version 7.0.0, connect operations have been simplified to always create a new relationship between nodes, regardless of whether a relationship already exists. See Connect operations now support multiple relationships between the same nodes.
If you must update an existing relationship instead of creating a new one, use the update
operation:
mutation {
updateMovies(
update: {
actors: [
{
where: { node: { name: { eq: "Keanu" } } }
update: { edge: { role: { set: "Neo" } } }
}
]
}
) {
movies {
title
}
}
}
Removed support for connectOrCreate
operations
The connectOrCreate
operation has been removed due to limitations on its feature set when compared to other operations.
Removed aggregate fields outside connection fields
Deprecated aggregate fields have been removed from the schema. Use aggregation fields within the connection selection set instead.
Before | After |
---|---|
|
|
The deprecatedAggregateOperations
option has been removed from the excludeDeprecatedFields
setting.
Subscriptions are now opt-in
Subscriptions are no longer automatically generated for all @node
types.
In version 7.x, the @subscription
directive is required to enable this functionality.
You must now explicitly enable subscriptions using the @subscription
directive in one of two ways:
-
At the schema level to enable subscriptions for all types:
extend schema @subscription
-
At the type level to enable subscriptions only for specific types:
type Movie @node @subscription {
title: String!
}
Removed publish
method from Neo4jGraphQLSubscriptionsEngine
The publish
method has been removed from the Neo4jGraphQLSubscriptionsEngine
interface as it is no longer used with Change Data Capture (CDC) based subscriptions.
Implementing this method on custom engines will no longer have an effect, and it is no longer possible to call publish
directly on Neo4jGraphQLSubscriptionsCDCEngine
.
Connect operations now support multiple relationships between the same nodes
The connect operations have been enhanced to support creating multiple relationships between the same pair of nodes. When performing a connect operation between two nodes, a new relationship is always created, even if one or more relationships of the same type already exist between those nodes. This enables modeling scenarios where multiple distinct relationships of the same type are needed between the same nodes. For example, an actor playing multiple roles in the same movie.
Changed behavior for multiple relationships between nodes
The way multiple relationships are handled between the same two nodes has changed:
-
In entity query fields (like
movies
), duplicate nodes are removed and only distinct results are returned, regardless of how many relationships exist between the nodes. -
In connection query fields (like
moviesConnection
), all relationships are represented individually. This allows for projecting relationship properties for each connection between the two nodes.
For example, consider a scenario where Eddie Murphy played multiple roles in the same movie:
CREATE (eddie:Person {name: "Eddie Murphy"})
CREATE (nuttyProfessor:Movie { title: "The Nutty Professor" })
CREATE (eddie)-[:ACTED_IN { role: "Professor"}]->(nuttyProfessor)
CREATE (eddie)-[:ACTED_IN { role: "Buddy Love"}]->(nuttyProfessor)
With a standard entity query:
{
actors(where: {name: {eq: "Eddie Murphy"}}) {
name
movies {
title
}
}
}
The result shows "The Nutty Professor" only once (nodes are deduplicated):
{
"actors": [
{
"name": "Eddie Murphy",
"movies": [
{
"title": "The Nutty Professor"
}
]
}
]
}
However, with a connection query:
{
actors(where: { name: { eq: "Eddie Murphy" } }) {
name
moviesConnection {
edges {
properties {
role
}
node {
title
}
}
}
}
}
The result preserves both relationships with their distinct properties:
{
"actors": [
{
"name": "Eddie Murphy",
"moviesConnection": {
"edges": [
{
"properties": {
"role": "Professor"
},
"node": {
"title": "The Nutty Professor"
}
},
{
"properties": {
"role": "Buddy Love"
},
"node": {
"title": "The Nutty Professor"
}
}
]
}
}
]
}
Removed the @private
directive
This directive was intended to be used with the library @neo4j/graphql-ogm
which is no longer supported.
Schema generation avoids conflicting plural names
Schema generation now detects conflicting plural names in types and fails intentionally.
For example, the following schema will fail due to ambiguous Techs
plural:
type Tech @node(plural: "Techs") {
name: String
}
type Techs @node {
value: String
}
Full-text search changes
The @fulltext
directive has been significantly changed and now requires an index name, a query name and fields to be indexed:
"""
Informs @neo4j/graphql that there should be a fulltext index in the database, allowing users to search by the index in the generated schema.
"""
directive @fulltext(
indexes: [FulltextInput!]!
) on OBJECT
input FulltextInput {
indexName: String!
queryName: String!
fields: [String!]!
}
Here is an example of how to use it:
type Movie @node @fulltext(
indexes: [
{
indexName: "movieTitleIndex"
queryName: "moviesByTitle"
fields: ["title"]
}
]
) {
title: String!
}
Full-text search was previously available in two different locations. The following form has been completely removed:
# No longer supported
{
movies(fulltext: { movieTitleIndex: { phrase: "The Matrix" } }) {
title
}
}
The root-level query has been changed to use the Relay Cursor Connections Specification:
Before | After |
---|---|
|
|
The new form uses the Relay Cursor Connections Specification, which allows for pagination using cursors and access to the pageInfo
field.
Removed implicit set operations
Implicit set operations in update mutations have been removed and the migration path involves two steps:
Before | After |
---|---|
|
|
The implicitSet
option of excludeDeprecatedFields
has been removed.
@coalesce
directive affects projection field values
In version 7.0.0, the @coalesce
directive now applies to both filter operations and field projections.
Previously, the fallback values specified in the @coalesce
directive were only used when those fields were used in filters, but not on the returned fields.
Now, when you select a field annotated with @coalesce
, null values are replaced with the specified fallback value in the query result.
Consider this schema:
type Movie @node {
title: String!
rating: Float @coalesce(value: 5.0)
}
Previously, requesting a movie with a null rating would return:
query {
movies {
title
rating # Would return null if the rating wasn't set
}
}
{
"movies": [
{
"title": "The Matrix",
"rating": null
}
]
}
Now, the same query returns the coalesced value:
{
"movies": [
{
"title": "The Matrix",
"rating": 5.0 # Returns the fallback value specified in @coalesce
}
]
}
This ensures consistent behavior between filtering and projecting fields with the @coalesce
directive.
Set addVersionPrefix
to true by default
In version 7.0.0, the addVersionPrefix
option is set to true
by default.
This means that all generated Cypher queries are automatically prefixed with the Cypher version:
CYPHER 5
MATCH(this:Movie)
This ensures that the correct Cypher version is used when queries are executed in Neo4j. However, this change may be incompatible with older versions of Neo4j.
Set cypherQueryOptions.addVersionPrefix
to false
to disable this behavior:
{
cypherQueryOptions: {
addVersionPrefix: false,
},
}
For example, when using Apollo Server:
await startStandaloneServer(server, {
context: async ({ req }) => ({
req,
cypherQueryOptions: {
addVersionPrefix: false,
},
}),
listen: { port: 4000 },
});
Changed DateTime
and Time
value conversion behavior
In version 7.0.0, DateTime
and Time
values are converted from Strings to temporal types directly in the generated Cypher queries, rather than in server code using the Neo4j driver.
For example, if you have a date String in your GraphQL query:
query {
movies(where: { releaseDate: { gt: "2023-01-15T12:30:00Z" } }) {
title
releaseDate
}
}
The string value "2023-01-15T12:30:00Z" is now converted to a temporal type directly in the Cypher query:
MATCH (this:Movie)
WHERE this.releaseDate > datetime($param0)
RETURN this { .title, .releaseDate } as this
Mutation operations follow relationship directions
Mutation operations now respect the queryDirection
value defined in the @relationship
directive when matching existing relationships.
This ensures consistent behavior between queries and mutations regarding how relationships are traversed.
When creating new relationships, the physical direction stored in the database is still determined by the `direction` argument. The change affects only how existing relationships are matched during mutation operations. |
For example, consider the following schema:
type Movie @node {
title: String!
actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, queryDirection: UNDIRECTED)
}
type Person @node {
name: String!
movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, queryDirection: UNDIRECTED)
}
With queryDirection: UNDIRECTED
, mutations now traverse existing relationships in both directions when matching nodes, regardless of the base direction
value.
This is consistent with how queries work with the same directive configuration.
Previously, mutations would always follow the explicit direction
value when matching existing relationships, which could lead to inconsistent behavior between queries and mutations.
Moved where
field in nested update operations
The where
field for nested update operations has been removed in favor of the where
inside the nested update input.
Before | After |
---|---|
|
|
Changed behavior for single
and some
filters on relationships to unions
The behavior of single
and some
filters when used with relationships to union types has been fixed, which represents a breaking change from the previous incorrect implementation.
Consider this schema with a union type and a relationship to it:
union Production = Movie | Series
type Actor @node {
name: String!
actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT)
}
Previously, when using single
or some
filters with unions, conditions inside these filters were evaluated separately for each concrete type in the union, requiring all of them to match.
This behavior was incorrect.
query {
actors(
where: {
actedIn: { single: { Movie: { title: { contains: "The Office" } }, Series: { title: { endsWith: "Office" } } } }
}
) {
name
}
}
The fixed behavior in version 7.0.0:
-
single
: Now correctly returns actors with exactly one related node across the whole union, rather than per type. -
some
: Now correctly returns actors with at least one matching related node of any type in the union.
This change provides more logical and consistent results, but may affect existing queries that relied on the previous behavior.
This fix applies to both the new filter syntax and the deprecated filters (e.g., actedIn_SINGLE
and actedIn_SOME
).
List of nullable elements no longer supported
A list of nullable elements is no longer supported in types annotated with the @node
directive, for example in the following type definition:
type Actor @node {
name: String
pseudonyms: [String]!
}
This is due the fact that Neo4j does not support storing null values as part of a list.
To create a similar but supported type definition, change the value of the pseudonyms
field to a non-nullable list: [String!]!
.
Interfaces and unions disallow mixing types with and without the @node
directive
Interfaces and unions can only be implemented by types that are all annotated with the @node
directive or none of them.
Type definitions like the following are no longer supported because the Series
type is missing the @node
directive:
interface Production @node {
title: String
}
type Movie @node implements Production {
title: String
}
type Series implements Production {
title: String
}
Deprecations
Deprecated mutations operators outside dedicated input
The following operators are now deprecated:
-
_SET
-
_POP
-
_PUSH
-
_INCREMENT
-
_ADD
-
_DECREMENT
-
_SUBTRACT
-
_MULTIPLY
-
_DIVIDE
Use the dedicated input object versions instead:
Before | After |
---|---|
|
|
The excludeDeprecatedFields
option now contains the option mutationOperations
to remove these deprecated operators:
const neoSchema = new Neo4jGraphQL({
typeDefs,
excludeDeprecatedFields: {
mutationOperations: true
}
});
Deprecated the _EQ filter syntax
The _EQ
filter syntax is now deprecated.
Use the dedicated input object versions instead:
Before | After |
---|---|
|
|
The excludeDeprecatedFields
option now contains the option attributeFilters
to remove these deprecated filters:
const neoSchema = new Neo4jGraphQL({
typeDefs,
excludeDeprecatedFields: {
attributeFilters: true
}
});
Deprecated aggregation filters outside connection fields
The aggregation filters outside connection fields are now deprecated. Use the aggregation filters within connection input fields instead:
Before | After |
---|---|
|
|
The excludeDeprecatedFields
option now contains the option aggregationFiltersOutsideConnection
to remove these deprecated filters:
const neoSchema = new Neo4jGraphQL({
typeDefs,
excludeDeprecatedFields: {
aggregationFiltersOutsideConnection: true
}
});
Deprecated aggregation filters outside dedicated input
Aggregation filters outside dedicated input like _AVERAGE_GT
are now deprecated.
Use the dedicated input object versions instead:
Before | After |
---|---|
|
|
The excludeDeprecatedFields
option now contains the option aggregationFilters
to remove these deprecated filters:
const neoSchema = new Neo4jGraphQL({
typeDefs,
excludeDeprecatedFields: {
aggregationFilters: true
}
});
Deprecated relationship filters outside dedicated input
The relationship filtering syntax outside dedicated input like _SOME
, _ALL
, and _NONE
is now deprecated.
Use the dedicated input object versions instead:
Before | After |
---|---|
|
|
The excludeDeprecatedFields
option now contains the option relationshipFilters
to remove these deprecated filters:
const neoSchema = new Neo4jGraphQL({
typeDefs,
excludeDeprecatedFields: {
relationshipFilters: true
}
});