neo4j-graphql-skill

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

When to Use

适用场景

  • Creating a GraphQL API from a Neo4j graph schema with
    @neo4j/graphql
  • Writing type definitions with
    @relationship
    ,
    @cypher
    ,
    @authorization
    directives
  • Using OGM for server-side programmatic Neo4j access (bypasses GraphQL auth)
  • Configuring auto-generated queries, mutations, subscriptions
  • Securing types/fields with JWT or JWKS-based
    @authorization
    rules
  • Migrating from v5/v6 to v7 (breaking changes below)
  • 使用
    @neo4j/graphql
    从Neo4j图模式创建GraphQL API
  • 编写带有
    @relationship
    @cypher
    @authorization
    指令的类型定义
  • 使用OGM实现服务器端编程式Neo4j访问(绕过GraphQL权限验证)
  • 配置自动生成的查询、变更、订阅
  • 使用基于JWT或JWKS的
    @authorization
    规则保护类型/字段
  • 从v5/v6迁移到v7(下方列出破坏性变更)

When NOT to Use

不适用场景

  • Raw Cypher queries outside GraphQL resolvers
    neo4j-cypher-skill
  • Spring Data Neo4j / Java entity mapping
    neo4j-spring-data-skill
  • Generic GraphQL without Neo4j — outside scope

  • 解析器之外的原生Cypher查询 → 使用
    neo4j-cypher-skill
  • Spring Data Neo4j / Java实体映射 → 使用
    neo4j-spring-data-skill
  • 不涉及Neo4j的通用GraphQL开发 —— 超出本范围

Version Matrix

版本矩阵

VersionStatusNotes
v7Current
@node
required;
options
removed; explicit
eq
syntax
v5LTSOlder syntax;
options: {limit, offset, sort}
still valid
Default to v7 unless codebase is on v5.

版本状态说明
v7当前版本必须使用
@node
;移除
options
;使用显式
eq
语法
v5长期支持版旧语法;
options: {limit, offset, sort}
仍有效
除非代码库使用v5版本,否则默认使用v7。

Step 1 — Install

步骤1 — 安装

bash
npm install @neo4j/graphql neo4j-driver graphql @apollo/server
For subscriptions (CDC required):
bash
npm install ws graphql-ws express body-parser cors

bash
npm install @neo4j/graphql neo4j-driver graphql @apollo/server
如需订阅功能(需启用CDC):
bash
npm install ws graphql-ws express body-parser cors

Step 2 — Minimal Server Setup

步骤2 — 最小化服务器配置

javascript
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { Neo4jGraphQL } from '@neo4j/graphql';
import neo4j from 'neo4j-driver';

const typeDefs = `#graphql
  type Movie @node {
    id: ID! @id
    title: String!
    actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN)
  }

  type Person @node {
    id: ID! @id
    name: String!
    movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
  }
`;

const driver = neo4j.driver(
  process.env.NEO4J_URI,
  neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
);

const neoSchema = new Neo4jGraphQL({ typeDefs, driver });

// assertIndexesAndConstraints syncs @id → UNIQUE constraints; wrap in try/catch
await neoSchema.assertIndexesAndConstraints({ options: { create: true } });

const server = new ApolloServer({ schema: await neoSchema.getSchema() });

const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({ token: req.headers.authorization }),
  listen: { port: 4000 },
});
assertIndexesAndConstraints
throws if constraints missing. Use
{ create: true }
to auto-create, or run
CREATE CONSTRAINT
manually and retry.

javascript
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { Neo4jGraphQL } from '@neo4j/graphql';
import neo4j from 'neo4j-driver';

const typeDefs = `#graphql
  type Movie @node {
    id: ID! @id
    title: String!
    actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN)
  }

  type Person @node {
    id: ID! @id
    name: String!
    movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
  }
`;

const driver = neo4j.driver(
  process.env.NEO4J_URI,
  neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
);

const neoSchema = new Neo4jGraphQL({ typeDefs, driver });

// assertIndexesAndConstraints会同步@id到UNIQUE约束;需包裹在try/catch中
await neoSchema.assertIndexesAndConstraints({ options: { create: true } });

const server = new ApolloServer({ schema: await neoSchema.getSchema() });

const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({ token: req.headers.authorization }),
  listen: { port: 4000 },
});
assertIndexesAndConstraints
会在约束缺失时抛出异常。使用
{ create: true }
自动创建,或手动执行
CREATE CONSTRAINT
后重试。

Key Directives

核心指令

@node (v7 required)

@node(v7必填)

Every GraphQL type representing a Neo4j node must have
@node
. Without it, v7 ignores the type.
graphql
type Product @node {
  id: ID! @id
  name: String!
}
每个代表Neo4j节点的GraphQL类型必须添加
@node
。如果没有,v7会忽略该类型。
graphql
type Product @node {
  id: ID! @id
  name: String!
}

Custom label (default = type name)

自定义标签(默认值为类型名称)

type Article @node(labels: ["Post", "Content"]) { title: String! }
undefined
type Article @node(labels: ["Post", "Content"]) { title: String! }
undefined

@relationship — Full Syntax

@relationship — 完整语法

graphql
type Person @node {
  # direction: OUT = (this)-[:KNOWS]->(other)
  friends: [Person!]! @relationship(type: "KNOWS", direction: OUT)

  # direction: IN = (other)-[:ACTED_IN]->(this)
  actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: IN)

  # direction: UNDIRECTED = matches both directions (use sparingly — double-counts)
  colleagues: [Person!]! @relationship(type: "COLLEAGUE_OF", direction: UNDIRECTED)

  # Relationship with properties — reference an @relationshipProperties interface
  reviews: [Movie!]! @relationship(type: "REVIEWED", direction: OUT, properties: "ReviewedProps")
}

interface ReviewedProps @relationshipProperties {
  rating: Int!
  date: Date
}
Direction rule:
OUT
= arrow leaves this node.
IN
= arrow enters this node. Both sides of a relationship must declare opposite directions.
graphql
type Person @node {
  # direction: OUT = (this)-[:KNOWS]->(other)
  friends: [Person!]! @relationship(type: "KNOWS", direction: OUT)

  # direction: IN = (other)-[:ACTED_IN]->(this)
  actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: IN)

  # direction: UNDIRECTED = 匹配双向关系(谨慎使用——会重复计数)
  colleagues: [Person!]! @relationship(type: "COLLEAGUE_OF", direction: UNDIRECTED)

  # 带属性的关系——引用@relationshipProperties接口
  reviews: [Movie!]! @relationship(type: "REVIEWED", direction: OUT, properties: "ReviewedProps")
}

interface ReviewedProps @relationshipProperties {
  rating: Int!
  date: Date
}
方向规则:
OUT
= 箭头从当前节点出发。
IN
= 箭头指向当前节点。关系的两端必须声明相反的方向。

Querying Relationship Properties — Connection API

查询关系属性 — Connection API

For each relationship with
properties:
, a
{field}Connection
field is auto-generated. Access rel properties via
actorsConnection.edges.properties
, not via
actors
:
graphql
query {
  movies(where: { title: { eq: "The Matrix" } }) {
    title
    actorsConnection {
      edges {
        properties { role }   # maps to @relationshipProperties interface
        node { name }
      }
    }
  }
}
对于每个带有
properties:
的关系,会自动生成
{field}Connection
字段。需通过
actorsConnection.edges.properties
访问关系属性,而非
actors
graphql
query {
  movies(where: { title: { eq: "The Matrix" } }) {
    title
    actorsConnection {
      edges {
        properties { role }   # 映射到@relationshipProperties接口
        node { name }
      }
    }
  }
}

@cypher — Custom Resolver

@cypher — 自定义解析器

graphql
type Person @node {
  name: String!

  # columnName must exactly match the RETURN alias — mismatch returns null silently
  friendCount: Int
    @cypher(
      statement: "MATCH (this)-[:KNOWS]->(f:Person) RETURN count(f) AS friendCount"
      columnName: "friendCount"
    )

  recommendedMovies: [Movie!]!
    @cypher(
      statement: """
        MATCH (this)-[:WATCHED]->(m:Movie)<-[:WATCHED]-(o:Person)-[:WATCHED]->(rec:Movie)
        WHERE NOT (this)-[:WATCHED]->(rec)
        RETURN rec
      """
      columnName: "rec"
    )
}
graphql
type Person @node {
  name: String!

  # columnName必须与RETURN别名完全匹配——不匹配会静默返回null
  friendCount: Int
    @cypher(
      statement: "MATCH (this)-[:KNOWS]->(f:Person) RETURN count(f) AS friendCount"
      columnName: "friendCount"
    )

  recommendedMovies: [Movie!]!
    @cypher(
      statement: """
        MATCH (this)-[:WATCHED]->(m:Movie)<-[:WATCHED]-(o:Person)-[:WATCHED]->(rec:Movie)
        WHERE NOT (this)-[:WATCHED]->(rec)
        RETURN rec
      """
      columnName: "rec"
    )
}

@cypher on Query field — custom top-level query

@cypher用于Query字段——自定义顶级查询

type Query { topRatedMovies(limit: Int = 10): [Movie!]! @cypher( statement: "MATCH (m:Movie) WHERE m.rating IS NOT NULL RETURN m ORDER BY m.rating DESC LIMIT $limit" columnName: "m" ) }

`this` refers to the current node in field-level @cypher. Parameters are passed as `$paramName`.
type Query { topRatedMovies(limit: Int = 10): [Movie!]! @cypher( statement: "MATCH (m:Movie) WHERE m.rating IS NOT NULL RETURN m ORDER BY m.rating DESC LIMIT $limit" columnName: "m" ) }

`this`在字段级@cypher中代表当前节点。参数通过`$paramName`传递。

@cypher — Field Arguments and extend type

@cypher — 字段参数与扩展类型

graphql
undefined
graphql
undefined

extend type adds computed fields without modifying the base type definition

extend type用于添加计算字段,无需修改基础类型定义

extend type Movie @node { avgRating: Float @cypher(statement: "MATCH (this)<-[r:RATED]-(:User) RETURN avg(r.rating) AS result", columnName: "result")

Field arguments passed as Cypher params; always provide default to avoid null

recommended(limit: Int = 3): [Movie!]! @cypher( statement: "MATCH (this)<-[:RATED]-(u:User)-[:RATED]->(rec:Movie) WITH rec, COUNT(u) AS score ORDER BY score DESC RETURN rec LIMIT $limit" columnName: "rec" ) }
undefined
extend type Movie @node { avgRating: Float @cypher(statement: "MATCH (this)<-[r:RATED]-(:User) RETURN avg(r.rating) AS result", columnName: "result")

字段参数作为Cypher参数传递;始终提供默认值以避免null

recommended(limit: Int = 3): [Movie!]! @cypher( statement: "MATCH (this)<-[:RATED]-(u:User)-[:RATED]->(rec:Movie) WITH rec, COUNT(u) AS score ORDER BY score DESC RETURN rec LIMIT $limit" columnName: "rec" ) }
undefined

@id and @timestamp

@id和@timestamp

graphql
type Post @node {
  id: ID! @id                          # auto-generates UUID; creates UNIQUE constraint
  createdAt: DateTime! @timestamp(operations: [CREATE])
  updatedAt: DateTime @timestamp(operations: [CREATE, UPDATE])
  title: String!
}
graphql
type Post @node {
  id: ID! @id                          # 自动生成UUID;创建UNIQUE约束
  createdAt: DateTime! @timestamp(operations: [CREATE])
  updatedAt: DateTime @timestamp(operations: [CREATE, UPDATE])
  title: String!
}

@alias — Map GraphQL field to Neo4j property

@alias — 将GraphQL字段映射到Neo4j属性

graphql
type User @node {
  id: ID! @id
  email: String! @alias(property: "emailAddress")  # GraphQL: email → DB: emailAddress
}

graphql
type User @node {
  id: ID! @id
  email: String! @alias(property: "emailAddress")  # GraphQL: email → 数据库: emailAddress
}

Security — @authentication and @authorization

安全验证 — @authentication和@authorization

Step 1: Configure JWT in constructor

步骤1:在构造函数中配置JWT

javascript
// Symmetric secret
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: {
    authorization: { key: process.env.JWT_SECRET },
  },
});

// JWKS endpoint (production)
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: {
    authorization: {
      key: { url: 'https://myapp.com/.well-known/jwks.json' },
    },
  },
});
javascript
// 对称密钥
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: {
    authorization: { key: process.env.JWT_SECRET },
  },
});

// JWKS端点(生产环境)
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: {
    authorization: {
      key: { url: 'https://myapp.com/.well-known/jwks.json' },
    },
  },
});

Step 2: Pass token in context

步骤2:在上下文传递token

javascript
context: async ({ req }) => ({ token: req.headers.authorization }),
// Or pass pre-decoded JWT:
context: async ({ req }) => ({ jwt: myDecodeJwt(req.headers.authorization) }),
javascript
context: async ({ req }) => ({ token: req.headers.authorization }),
// 或传递预解码的JWT:
context: async ({ req }) => ({ jwt: myDecodeJwt(req.headers.authorization) }),

Step 3: Apply @authentication and @authorization

步骤3:应用@authentication和@authorization

graphql
undefined
graphql
undefined

Require auth on all operations for a type

对类型的所有操作要求身份验证

type Post @node @authentication @authorization(filter: [{ where: { node: { author: { id: { eq: "$jwt.sub" } } } } }]) { title: String! author: User! @relationship(type: "AUTHORED", direction: IN) }
type Post @node @authentication @authorization(filter: [{ where: { node: { author: { id: { eq: "$jwt.sub" } } } } }]) { title: String! author: User! @relationship(type: "AUTHORED", direction: IN) }

requireAuthentication: false = allow public access without JWT

requireAuthentication: false = 允许无需JWT的公共访问

type Article @node @authorization(filter: [ { requireAuthentication: false, where: { node: { published: { eq: true } } } } { where: { node: { author: { id: { eq: "$jwt.sub" } } } } } ]) { title: String! published: Boolean! }
type Article @node @authorization(filter: [ { requireAuthentication: false, where: { node: { published: { eq: true } } } } { where: { node: { author: { id: { eq: "$jwt.sub" } } } } } ]) { title: String! published: Boolean! }

validate (throws error) vs filter (silently hides data)

validate(抛出错误)vs filter(静默隐藏数据)

type BankAccount @node @authorization(validate: [{ when: [BEFORE], where: { node: { owner: { id: { eq: "$jwt.sub" } } } } }]) { balance: Float! }
type BankAccount @node @authorization(validate: [{ when: [BEFORE], where: { node: { owner: { id: { eq: "$jwt.sub" } } } } }]) { balance: Float! }

Role-based with custom JWT claims

基于角色的自定义JWT声明

type JWT @jwt { roles: [String!]! @jwtClaim(path: "myApp.roles") }
type AdminReport @node @authentication(operations: [READ], jwt: { roles: { includes: "admin" } }) { data: String! }

**filter vs validate**: `filter` silently removes unauthorized data. `validate` throws an error. Use `validate` when data existence should not be revealed to unauthorized users.

**BEFORE vs AFTER**: `CREATE` supports only `AFTER`; `READ` supports only `BEFORE`.

---
type JWT @jwt { roles: [String!]! @jwtClaim(path: "myApp.roles") }
type AdminReport @node @authentication(operations: [READ], jwt: { roles: { includes: "admin" } }) { data: String! }

**filter与validate的区别**:`filter`会静默移除未授权数据。`validate`会抛出错误。当不应向未授权用户透露数据存在性时,使用`validate`。

**BEFORE与AFTER**:`CREATE`仅支持`AFTER`;`READ`仅支持`BEFORE`。

---

Auto-Generated Operations

自动生成的操作

For each
@node
type, the library generates:
OperationGenerated NameExample
Query all
{plural}
movies(where, sort, limit, offset)
Cursor pagination
{plural}Connection
moviesConnection(first, after, where, sort)
Create
create{Plural}
createMovies(input: [MovieCreateInput!]!)
Update
update{Plural}
updateMovies(where, update)
Delete
delete{Plural}
deleteMovies(where, delete)
对于每个
@node
类型,库会自动生成以下操作:
操作生成名称示例
查询所有
{plural}
movies(where, sort, limit, offset)
游标分页
{plural}Connection
moviesConnection(first, after, where, sort)
创建
create{Plural}
createMovies(input: [MovieCreateInput!]!)
更新
update{Plural}
updateMovies(where, update)
删除
delete{Plural}
deleteMovies(where, delete)

v7 Filter Syntax (explicit
eq
)

v7过滤语法(显式
eq

graphql
undefined
graphql
undefined

v7: explicit eq required

v7: 必须显式使用eq

query { movies(where: { title: { eq: "The Matrix" } }) { title actors { name } } }
query { movies(where: { title: { eq: "The Matrix" } }) { title actors { name } } }

Sort and paginate (v7: direct args, not options wrapper)

排序和分页(v7: 直接传参,而非options包装器)

query { movies(sort: [{ title: ASC }], limit: 10, offset: 0) { title } }
undefined
query { movies(sort: [{ title: ASC }], limit: 10, offset: 0) { title } }
undefined

Nested Mutations

嵌套变更

graphql
mutation {
  createMovies(input: [{
    title: "Inception"
    actors: {
      create: [{ node: { name: "Leonardo DiCaprio" } }]
      connect: { where: { node: { name: { eq: "Joseph Gordon-Levitt" } } } }
    }
  }]) {
    movies { id title }
  }
}
connectOrCreate
was removed in v7. Use
connect
+
create
separately.

graphql
mutation {
  createMovies(input: [{
    title: "Inception"
    actors: {
      create: [{ node: { name: "Leonardo DiCaprio" } }]
      connect: { where: { node: { name: { eq: "Joseph Gordon-Levitt" } } } }
    }
  }]) {
    movies { id title }
  }
}
connectOrCreate
在v7中已移除。请分别使用
connect
+
create

OGM — Programmatic Access

OGM — 编程式访问

OGM bypasses GraphQL authorization — use only in trusted server-side contexts.
javascript
import { OGM } from '@neo4j/graphql-ogm';

const ogm = new OGM({ typeDefs, driver });
await ogm.init();  // must await before using models

const Movie = ogm.model('Movie');

// find
const movies = await Movie.find({
  where: { title: { eq: 'The Matrix' } },
  selectionSet: `{ id title actors { name } }`,
});

// create
const { movies: created } = await Movie.create({
  input: [{ title: 'Dune', actors: { create: [{ node: { name: 'Timothée Chalamet' } }] } }],
});

// update
await Movie.update({
  where: { id: { eq: movieId } },
  update: { title: { set: 'Dune: Part One' } },
});

// delete
await Movie.delete({ where: { id: { eq: movieId } } });
Install separately:
npm install @neo4j/graphql-ogm

OGM会绕过GraphQL权限验证——仅在受信任的服务器端上下文使用。
javascript
import { OGM } from '@neo4j/graphql-ogm';

const ogm = new OGM({ typeDefs, driver });
await ogm.init();  // 使用模型前必须等待初始化完成

const Movie = ogm.model('Movie');

// 查询
const movies = await Movie.find({
  where: { title: { eq: 'The Matrix' } },
  selectionSet: `{ id title actors { name } }`,
});

// 创建
const { movies: created } = await Movie.create({
  input: [{ title: 'Dune', actors: { create: [{ node: { name: 'Timothée Chalamet' } }] } }],
});

// 更新
await Movie.update({
  where: { id: { eq: movieId } },
  update: { title: { set: 'Dune: Part One' } },
});

// 删除
await Movie.delete({ where: { id: { eq: movieId } } });
需单独安装:
npm install @neo4j/graphql-ogm

Subscriptions (CDC Required)

订阅功能(需启用CDC)

Requires Neo4j CDC enabled in
FULL
mode. See CDC docs.
javascript
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: { subscriptions: true },
});
Subscriptions auto-generate for each type:
graphql
subscription {
  movieCreated(where: { title: { eq: "The Matrix" } }) {
    createdMovie { title }
  }
}
要求Neo4j CDC以
FULL
模式启用。请查看CDC文档
javascript
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: { subscriptions: true },
});
每个类型会自动生成订阅:
graphql
subscription {
  movieCreated(where: { title: { eq: "The Matrix" } }) {
    createdMovie { title }
  }
}

Also: movieUpdated, movieDeleted

还有:movieUpdated, movieDeleted


---

---

Schema Control Directives

模式控制指令

graphql
type ReadOnlyData @node @mutation(operations: []) { value: String! }  # disable mutations

type HeavyDoc @node {
  id: ID! @id
  content: String! @filterable(byValue: false) @sortable(enabled: false)  # perf guard
  title: String!
}

type Series @node @plural(value: "seriesList") { title: String! }  # irregular plural fix

graphql
type ReadOnlyData @node @mutation(operations: []) { value: String! }  # 禁用变更操作

type HeavyDoc @node {
  id: ID! @id
  content: String! @filterable(byValue: false) @sortable(enabled: false)  # 性能防护
  title: String!
}

type Series @node @plural(value: "seriesList") { title: String! }  # 修复不规则复数

Common Errors

常见错误

ErrorCauseFix
Type 'X' not found
Missing
@node
on type (v7)
Add
@node
to every node type
@cypher
field returns null
columnName
mismatch with RETURN alias
Match
columnName
exactly to RETURN alias
Relationship direction mismatchBoth sides declare same directionInverse: if A has
direction: OUT
, B must have
direction: IN
assertIndexesAndConstraints
throws
@id
constraint not in DB
Add
{ options: { create: true } }
or run
CREATE CONSTRAINT
manually
Auth not appliedJWT not in contextPass
token: req.headers.authorization
in context function
0 results with valid datav7 filter missing
eq
Use
{ field: { eq: value } }
not
{ field: value }
connectOrCreate not found
Removed in v7Use
connect
+
create
separately
Memory errors on large mutationsComplex Cypher generationBatch mutations; increase
server.memory.heap.max_size
@subscription
not generating
v7 requires explicit enableAdd
features: { subscriptions: true }
to constructor

错误原因修复方法
Type 'X' not found
类型缺少
@node
(v7)
为每个节点类型添加
@node
@cypher
字段返回null
columnName
与RETURN别名不匹配
确保
columnName
与RETURN别名完全一致
关系方向不匹配两端声明了相同的方向反转方向:如果A使用
direction: OUT
,B必须使用
direction: IN
assertIndexesAndConstraints
抛出异常
@id
约束不存在于数据库中
添加
{ options: { create: true } }
或手动执行
CREATE CONSTRAINT
权限验证未生效上下文未传递JWT在上下文函数中传递
token: req.headers.authorization
数据有效但返回0条结果v7过滤缺少
eq
使用
{ field: { eq: value } }
而非
{ field: value }
connectOrCreate not found
v7中已移除分别使用
connect
+
create
大型变更时内存错误Cypher生成过于复杂分批执行变更;增大
server.memory.heap.max_size
@subscription
未生成
v7需要显式启用在构造函数中添加
features: { subscriptions: true }

v6 → v7 Breaking Changes Summary

v6 → v7破坏性变更汇总

v6v7
@node
optional
@node
required on every node type
options: { limit, sort }
limit
,
sort
as direct args
{ field: value }
filter
{ field: { eq: value } }
connectOrCreate
nested mutation
Removed — use
connect
+
create
directed
arg on queries
queryDirection
in
@relationship
Single rel fields
actor: Person
Must use list
actors: [Person!]!
@private
directive
Removed
@unique
directive
Removed

v6v7
@node
可选
每个节点类型必须添加
@node
options: { limit, sort }
limit
sort
作为直接参数
{ field: value }
过滤
{ field: { eq: value } }
connectOrCreate
嵌套变更
已移除——使用
connect
+
create
查询中的
directed
参数
@relationship
中使用
queryDirection
单个关系字段
actor: Person
必须使用列表
actors: [Person!]!
@private
指令
已移除
@unique
指令
已移除

References

参考资料



Checklist

检查清单

  • @node
    on every GraphQL type representing a Neo4j node (v7 hard requirement)
  • @id
    on identity fields (triggers
    CREATE CONSTRAINT
    via
    assertIndexesAndConstraints
    )
  • assertIndexesAndConstraints
    called on startup with try/catch
  • @relationship
    direction correct:
    OUT
    = arrow leaves this node,
    IN
    = arrow enters
  • Both sides of relationship declared with inverse directions
  • @cypher
    columnName
    matches RETURN alias exactly
  • JWT secret or JWKS URL in
    features.authorization.key
    ; token passed in context
  • @authorization
    filter vs validate chosen deliberately (silent hide vs thrown error)
  • v7: filters use explicit
    { field: { eq: value } }
    syntax
  • v7:
    limit
    /
    sort
    passed as direct query args (not
    options
    wrapper)
  • OGM:
    await ogm.init()
    called before any
    ogm.model()
    usage
  • Subscriptions: CDC enabled in FULL mode before enabling
    features.subscriptions
  • .env
    holds credentials;
    .env
    in
    .gitignore
  • 每个代表Neo4j节点的GraphQL类型都添加了
    @node
    (v7硬性要求)
  • 标识字段添加了
    @id
    (通过
    assertIndexesAndConstraints
    触发
    CREATE CONSTRAINT
  • 启动时调用
    assertIndexesAndConstraints
    并包裹在try/catch中
  • @relationship
    方向正确:
    OUT
    = 箭头从当前节点出发,
    IN
    = 箭头指向当前节点
  • 关系两端声明了相反的方向
  • @cypher
    columnName
    与RETURN别名完全匹配
  • features.authorization.key
    中配置了JWT密钥或JWKS URL;上下文传递了token
  • 谨慎选择
    @authorization
    的filter或validate(静默隐藏 vs 抛出错误)
  • v7:过滤使用显式
    { field: { eq: value } }
    语法
  • v7:
    limit
    /
    sort
    作为查询直接参数传递(而非
    options
    包装器)
  • OGM:在调用任何
    ogm.model()
    前执行
    await ogm.init()
  • 订阅功能:在启用
    features.subscriptions
    前,已将CDC配置为FULL模式
  • 凭证存储在
    .env
    中;
    .env
    已加入
    .gitignore