Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
neo4j-contrib avatar

Neo4j Graphql Skill

  • 358 installs
  • 101 repo stars
  • Updated August 3, 2026
  • neo4j-contrib/neo4j-skills

neo4j-graphql-skill is an agent skill that configures a Neo4j-backed GraphQL API with @neo4j/graphql directives, auth, OGM, and Apollo integration.

About

neo4j-graphql-skill guides solo builders through building a GraphQL API backed by Neo4j using the Neo4j GraphQL Library—currently documented for v7 with v5 LTS paths. It covers constructor setup, index and constraint assertion, rich type definitions, relationship directions, custom @cypher resolvers, JWT/JWKS security on fields, auto-generated CRUD, OGM for trusted server-side access, subscriptions, and federation hooks with Apollo. Invoke it when you are writing typeDefs, securing fields, or wiring Neo4j to Apollo Server, and reach for neo4j-cypher-skill when you need Cypher outside GraphQL resolvers. The README marks the skill as draft/WIP; treat examples as living and verify against your installed @neo4j/graphql major version. Intermediate complexity assumes you already run a Neo4j instance and understand basic graph modeling.

  • Neo4jGraphQL constructor, getSchema(), and assertIndexesAndConstraints() setup
  • @node, @relationship (IN/OUT/UNDIRECTED), and @cypher custom resolver directives
  • @authorization and @authentication for JWT/JWKS field security
  • Auto-generated queries/mutations plus OGM programmatic access (bypasses GraphQL auth)
  • Subscriptions via CDC and Apollo Federation notes for @neo4j/graphql v7 (current) and v5 (LTS)

Neo4j Graphql Skill by the numbers

  • 358 all-time installs (skills.sh)
  • +27 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #1,137 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Security screen: MEDIUM risk (skills.sh audit)
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-graphql-skill

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs358
repo stars101
Security audit2 / 3 scanners passed
Last updatedAugust 3, 2026
Repositoryneo4j-contrib/neo4j-skills

What it does

Stand up a secured GraphQL API on Neo4j with @neo4j/graphql typeDefs, relationships, Cypher directives, and OGM access.

Who is it for?

Best when you're shipping graph-backed SaaS or internal tools and want declarative GraphQL over Neo4j instead of hand-rolled resolvers for every edge.

Skip if: Spring Data Neo4j entity mapping (use neo4j-spring-data-skill) or workloads that only need ad-hoc Cypher without a GraphQL surface.

When should I use this skill?

When creating a GraphQL API from Neo4j with @neo4j/graphql, writing typeDefs with @relationship/@cypher/@authorization, using OGM, or wiring Neo4j to Apollo Server.

What you get

You leave with typeDefs, schema generation steps, index/constraint checks, and integration patterns for Apollo Server and programmatic OGM access.

  • GraphQL typeDefs with Neo4j directives and relationship mappings
  • Schema bootstrap with indexes/constraints and auth configuration notes

By the numbers

  • Documents @neo4j/graphql v7 (current) and v5 (LTS)
  • Skill version 1.0.1 in frontmatter

Files

SKILL.mdMarkdownGitHub ↗

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)

When NOT to Use

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

---

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.

---

Step 1 — Install

npm install @neo4j/graphql neo4j-driver graphql @apollo/server

For subscriptions (CDC required):

npm install ws graphql-ws express body-parser cors

---

Step 2 — Minimal Server Setup

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.

---

Key Directives

@node (v7 required)

Every GraphQL type representing a Neo4j node must have @node. Without it, v7 ignores the type.

type Product @node {
  id: ID! @id
  name: String!
}

# Custom label (default = type name)
type Article @node(labels: ["Post", "Content"]) {
  title: String!
}

@relationship — Full Syntax

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.

Querying Relationship Properties — Connection API

For each relationship with properties:, a {field}Connection field is auto-generated. Access rel properties via actorsConnection.edges.properties, not via actors:

query {
  movies(where: { title: { eq: "The Matrix" } }) {
    title
    actorsConnection {
      edges {
        properties { role }   # maps to @relationshipProperties interface
        node { name }
      }
    }
  }
}

@cypher — Custom Resolver

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

# @cypher on Query field — custom top-level 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.

@cypher — Field Arguments and extend type

# extend type adds computed fields without modifying the base type definition
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"
    )
}

@id and @timestamp

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!
}

@alias — Map GraphQL field to Neo4j property

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

---

Security — @authentication and @authorization

Step 1: Configure JWT in constructor

// 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' },
    },
  },
});

Step 2: Pass token in context

context: async ({ req }) => ({ token: req.headers.authorization }),
// Or pass pre-decoded JWT:
context: async ({ req }) => ({ jwt: myDecodeJwt(req.headers.authorization) }),

Step 3: Apply @authentication and @authorization

# 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)
}

# requireAuthentication: false = allow public access without JWT
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)
type BankAccount @node
  @authorization(validate: [{
    when: [BEFORE],
    where: { node: { owner: { id: { eq: "$jwt.sub" } } } }
  }]) {
  balance: Float!
}

# Role-based with custom JWT claims
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.

---

Auto-Generated Operations

For each @node type, the library generates:

OperationGenerated NameExample
Query all{plural}movies(where, sort, limit, offset)
Cursor pagination{plural}ConnectionmoviesConnection(first, after, where, sort)
Createcreate{Plural}createMovies(input: [MovieCreateInput!]!)
Updateupdate{Plural}updateMovies(where, update)
Deletedelete{Plural}deleteMovies(where, delete)

v7 Filter Syntax (explicit eq)

# v7: explicit eq required
query {
  movies(where: { title: { eq: "The Matrix" } }) {
    title
    actors { name }
  }
}

# Sort and paginate (v7: direct args, not options wrapper)
query {
  movies(sort: [{ title: ASC }], limit: 10, offset: 0) {
    title
  }
}

Nested Mutations

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.

---

OGM — Programmatic Access

OGM bypasses GraphQL authorization — use only in trusted server-side contexts.

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

---

Subscriptions (CDC Required)

Requires Neo4j CDC enabled in FULL mode. See CDC docs.

const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  features: { subscriptions: true },
});

Subscriptions auto-generate for each type:

subscription {
  movieCreated(where: { title: { eq: "The Matrix" } }) {
    createdMovie { title }
  }
}
# Also: movieUpdated, movieDeleted

---

Schema Control Directives

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

---

Common Errors

ErrorCauseFix
Type 'X' not foundMissing @node on type (v7)Add @node to every node type
@cypher field returns nullcolumnName mismatch with RETURN aliasMatch 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 DBAdd { 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 eqUse { field: { eq: value } } not { field: value }
connectOrCreate not foundRemoved in v7Use connect + create separately
Memory errors on large mutationsComplex Cypher generationBatch mutations; increase server.memory.heap.max_size
@subscription not generatingv7 requires explicit enableAdd features: { subscriptions: true } to constructor

---

v6 → v7 Breaking Changes Summary

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 mutationRemoved — use connect + create
directed arg on queriesqueryDirection in @relationship
Single rel fields actor: PersonMust use list actors: [Person!]!
@private directiveRemoved
@unique directiveRemoved

---

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

Related skills

How it compares

Skill package for @neo4j/graphql API design—not a hosted Neo4j Aura substitute or a generic REST CRUD generator.

FAQ

Who is neo4j-graphql-skill for?

and small-team developers exposing Neo4j through GraphQL with Neo4j’s official library and Apollo-style servers.

When should I use neo4j-graphql-skill?

Use it in Build (backend) when creating a GraphQL API from a graph schema, writing @relationship/@cypher/@authorization typeDefs, or configuring OGM and subscriptions.

Is neo4j-graphql-skill safe to install?

Review the Security Audits panel on this page; the skill may use Bash and WebFetch and touches auth secrets for JWT/JWKS configuration.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.