Migration to 4.0.0
This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.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 3.0.0 to 4.0.0.
IExecutableSchemaDefinition
If you were passing any arguments from IExecutableSchemaDefinition
into the library other than typeDefs
and resolvers
, these are no longer supported.
config.enableDebug
The programmatic toggle for debug logging has been moved from config.enableDebug
to simply debug
.
Before | Now |
---|---|
|
|
driverConfig
Session configuration is now available only in the context under the sessionConfig
key.
Additionally, the bookmarks
key has been removed as it is no longer needed with the bookmark manager of the newer driver.
Before | Now |
---|---|
|
|
config.enableRegex
This has been replaced by MATCHES
in features.filters.
With this change comes more granularity in the feature configuration.
You can now enable the MATCHES
filter on String
and ID
fields separately:
Before | After |
---|---|
|
|
queryOptions
If you had a need to pass in Cypher query options for query tuning, this interface has been changed.
The config option queryOptions
has now become cypherQueryOptions
inside the context function, and it now accepts simple strings instead of enums.
This change reflects the fact that the Cypher query options are set on a per-request basis.
Before | Now |
---|---|
|
|
skipValidateTypeDefs
The argument has been moved to the top-level of the constructor input and renamed validate
, which defaults to true
.
If you started using the config.startupValidation
option, this has also been rolled into the same validate
setting for simplicity.
Likewise, the resolvers
option is now just a warning, and noDuplicateRelationshipFields
is now a mandatory check rolled into validate
.
Here is an example query of how it looks now:
Before | After |
---|---|
|
|
@cypher
The default behavior of the @cypher
directive regarding the translation has changed.
Instead of using apoc.cypher.runFirstColumnMany, it directly wraps the query within a CALL { }
subquery.
This update has proven to be more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be aliased.
On top of that, to improve performance, it is recommended to pass the returned alias in the property columnName
, to ensure the subquery is properly integrated into the larger query.
For example, the GraphQL query:
type query {
test: String! @cypher(statement: "RETURN 'hello'")
}
Would be translated to:
CALL {
RETURN 'hello'
}
WITH 'hello' AS this
RETURN this
Which is invalid in Neo4j 5.x.
To fix it, ensure the RETURN
elements are aliased:
type query {
test: String! @cypher(statement: "RETURN 'hello' as result")
}
Another way to use this update is through an experimental option with the columnName
flag in the @cypher
directive:
type query {
test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result")
}
Note that escaping strings are no longer needed in Neo4j GraphQL 4.0.0.
@fulltext
In version 4.0.0, a number of improvements have been made to full-text queries. These include the ability to return the full-text score, filter by the score and sorting by the score. However, these improvements required a number of breaking changes.
Full-text queries
Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query.
As a result, the following query is now invalid:
query {
movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) {
title
}
}
The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. They now accept the following arguments:
-
phrase
: specifies the string to search for in the full-text index. -
where
: accepts a min/max score as well as the normal filters available on a node. -
`sort: used to sort using the score and node attributes.
-
limit
: used to limit the number of results to the given integer. -
offset
: used to offset by the given number of results.
This means that, for the following type definition:
type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below.
title: String!
}
The following top-level query and type definitions would be generated by the library:
type Query {
movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]!
}
"""The result of a fulltext search on an index of Movie"""
type MovieFulltextResult {
score: Float
movies: Movie
}
"""The input for filtering a fulltext query on an index of Movie"""
input MovieFulltextWhere {
score: FloatWhere
movie: MovieWhere
}
"""The input for sorting a fulltext query on an index of Movie"""
input MovieFulltextSort {
score: SortDirection
movie: MovieSort
}
"""The input for filtering the score of a fulltext search"""
input FloatWhere {
min: Float
max: Float
}
This query can then be used to perform a full-text query:
query {
movieFulltextMovieTitle(
phrase: "Full Metal Jacket",
where: { score: min: 0.4 },
sort: [{ movie: { title: ASC } }],
limit: 5,
offset: 10
) {
score
movies {
title
}
}
}
And thus return results in the following format:
{
"data": {
"movieFulltextMovieTitle": [
{
"score": 0.44524085521698,
"movie": {
"title": "Full Moon High"
}
},
{
"score": 1.411118507385254,
"movie": {
"title": "Full Metal Jacket"
}
}
]
}
}
Argument changes
The following changes have been made to @fulltext
arguments:
-
queryName
has been added to specify a custom name for the top-level query that is generated. -
name
has been renamed toindexName
to avoid ambiguity with the newqueryName
argument.
These changes mean that the following type definition is now invalid:
type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) {
title: String!
}
The name
argument now needs to be replaced with indexName
:
type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) {
title: String!
}
As an example, the queryName
argument can be used as:
type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) {
title: String!
}
This means the top-level query is now moviesByTitle
instead of movieFulltextMovieTitle
:
type Query {
moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]!
}
Subscription options
Subscriptions are no longer configured as a plugin, but as a feature within the features
option.
Before | Now |
---|---|
|
|
Default subscriptions
The class Neo4jGraphQLSubscriptionsSingleInstancePlugin
is no longer exported.
Instead, the default subscriptions behavior can be enabled by setting the subscriptions
option to true
.
Before | Now |
---|---|
|
|
Neo4j GraphQL subscriptions AMQP package
The name of the interface underlying the subscriptions system has changed from Neo4jGraphQLSubscriptionsPlugin
to Neo4jGraphQLSubscriptionsEngine
.
If you were previously using the @neo4j/graphql-plugins-subscriptions-amqp
package, this has been changed to @neo4j/graphql-amqp-subscriptions-engine
to reflect this underlying change.
To keep using it, uninstall the previous package and install the new one:
npm uninstall @neo4j/graphql-plugins-subscriptions-amqp
npm install @neo4j/graphql-amqp-subscriptions-engine
Then update any imports:
From | To |
---|---|
|
|
And change the instantiations:
From | To |
---|---|
|
|
Custom subscription plugins
The underlying subscription system has not changed. Custom behavior can be implemented the same way, by creating a class implementing the interface described in Subscriptions engines.
However, if using TypeScript, the exported interface to implement these classes has been renamed from Neo4jGraphQLSubscriptionsPlugin
to Neo4jGraphQLSubscriptionsEngine
.
Updated directives
A number of directives and their arguments have been renamed in order to make using @neo4j/graphql
more intuitive.
Here is a table with all the changes:
Before | Now | Example |
---|---|---|
|
Properties in the alias directive are now automatically escaped using backticks.
If you were using backticks in the |
|
|
Renamed to |
Before
Now
|
|
Renamed to
|
Before
Now
|
|
Renamed to
|
Before
Now
Additional example
|
|
Replaced by |
|
|
Deprecated with all of its arguments removed and/or replaced. |
|
|
Removed from |
Invalid
plural type definition
Updated version
|
|
Removed from
|
Current equivalent to
label
Current equivalent to
additionalLabels
Current equivalent to both arguments
|
|
Removed and moved to |
Outdated example
Updated version using
@limit
|
|
Removed and replaced by the |
|
|
Aggregation operations are no longer generated by default.
They can be enabled case by case using the directives |
|
Relationship updates
Here are the changes and updates to @relationship
-related features.
Relationship types are now automatically escaped
Relationship types are now automatically escaped. If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library.
@relationshipProperties
now mandatory
Current changes require the distinction between interfaces that are used to specify relationship properties, and others.
Therefore, the @relationshipProperties
directive is now required on all relationship property interfaces.
If it is not included, an error is thrown.
As a result, in version 4.0.0, the following type definitions are invalid:
type Person {
name: String!
movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn")
}
type Movie {
title: String!
actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}
interface ActedIn {
screenTime: Int!
}
ActedIn
must be decorated with @relationshipProperties
:
interface ActedIn @relationshipProperties {
screenTime: Int!
}
Duplicate relationship fields are now checked for
In 3.0.0, it was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. Now, this kind of scenario is detected during schema generation and an error is thrown so developers are informed to fix the type definitions.
Here is an example of what is now considered invalid with these checks:
type Team {
player1: Person! @relationship(type: "PLAYS_IN", direction: IN)
player2: Person! @relationship(type: "PLAYS_IN", direction: IN)
backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN)
}
type Person {
teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT)
}
In this example, there are multiple fields in the Team
type which have the same Person
type, the same @relationship
type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between player1
, player2
and backupPlayers
. Selecting these fields would then return the same data.
These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input.
const neoSchema = new Neo4jGraphQL({
typeDefs,
validate: false,
});