Skip to content

GraphQL API Guide

NornicDB provides a full-featured GraphQL API powered by gqlgen, offering a flexible alternative to the Cypher query interface. The GraphQL API supports queries, mutations, and real-time subscriptions for live updates.

Quick Start

Endpoints

  • GraphQL Endpoint: POST /graphql
  • GraphQL Playground: GET /graphql/playground

Using the Playground

Navigate to http://localhost:7474/graphql/playground to access the interactive GraphQL Playground with full schema introspection and auto-complete.

Authentication: Add headers { "Authorization": "Bearer <your-token>" } in the playground settings to use authentication.

Core Features

  • Full CRUD operations on nodes and relationships
  • Advanced search with vector similarity and BM25
  • Cypher query pass-through for complex graph operations
  • Real-time subscriptions for live data updates
  • Schema introspection for auto-complete
  • Graph traversal queries with relationship filtering

Schema Overview

Core Types

type Node {
  id: ID!
  internalId: String!
  labels: [String!]!
  properties: JSON!
  createdAt: DateTime
  updatedAt: DateTime
  hasEmbedding: Boolean!
  embeddingDimensions: Int!

  # Traversal
  relationships(
    types: [String!]
    direction: RelationshipDirection
    limit: Int
  ): [Relationship!]!
  outgoing(types: [String!], limit: Int): [Relationship!]!
  incoming(types: [String!], limit: Int): [Relationship!]!
  neighbors(
    direction: RelationshipDirection
    relationshipTypes: [String!]
    labels: [String!]
    limit: Int
  ): [Node!]!
  similar(limit: Int, threshold: Float): [SimilarNode!]!
}

type Relationship {
  id: ID!
  type: String!
  startNode: Node!
  endNode: Node!
  properties: JSON!
  createdAt: DateTime
  confidence: Float
  autoGenerated: Boolean!
}

Custom Scalars

  • JSON - Arbitrary JSON objects for properties
  • DateTime - RFC3339 timestamps
  • FloatArray - Float32 arrays for embeddings

Queries

Basic Queries

# Get database statistics
query Stats {
  stats {
    nodeCount
    relationshipCount
    embeddedNodeCount
    uptimeSeconds
    labels {
      label
      count
    }
  }
}

# Get a node by ID
query GetNode($id: ID!) {
  node(id: $id) {
    id
    labels
    properties
    createdAt
  }
}

# List nodes by label
query ListPersons {
  allNodes(labels: ["Person"], limit: 10) {
    id
    labels
    properties
  }
}

Search Queries

# Hybrid search (vector + BM25)
query Search {
  search(
    query: "software engineer"
    options: { limit: 10, labels: ["Person"], method: HYBRID }
  ) {
    results {
      node {
        id
        labels
        properties
      }
      score
      rrfScore
    }
    totalCount
    executionTimeMs
  }
}

# Find similar nodes
query FindSimilar {
  similar(nodeId: "node-id", limit: 5, threshold: 0.7) {
    node {
      id
      labels
      properties
    }
    similarity
  }
}

Graph Traversal

# Get node with relationships
query NodeWithRelationships {
  node(id: "person-id") {
    id
    labels
    outgoing(limit: 10) {
      type
      endNode {
        id
        labels
        properties
      }
    }
  }
}

# Find shortest path
query ShortestPath {
  shortestPath(
    startNodeId: "alice-id"
    endNodeId: "charlie-id"
    maxDepth: 5
    relationshipTypes: ["KNOWS"]
  ) {
    id
    labels
    properties
  }
}

Mutations

Creating Data

# Create a node
mutation CreatePerson {
  createNode(
    input: {
      labels: ["Person"]
      properties: { name: "Alice Smith", age: 30, email: "alice@example.com" }
    }
  ) {
    id
    labels
    properties
  }
}

# Create a relationship
mutation CreateKnows {
  createRelationship(
    input: {
      startNodeId: "alice-id"
      endNodeId: "bob-id"
      type: "KNOWS"
      properties: { since: "2024-01-01" }
    }
  ) {
    id
    type
    startNode {
      id
    }
    endNode {
      id
    }
  }
}

# Bulk create nodes
mutation BulkCreate {
  bulkCreateNodes(
    input: {
      nodes: [
        { labels: ["Person"], properties: { name: "Alice" } }
        { labels: ["Person"], properties: { name: "Bob" } }
      ]
    }
  ) {
    created
    skipped
    errors
  }
}

Updating Data

# Update node properties
mutation UpdatePerson {
  updateNode(
    input: { id: "node-id", properties: { age: 31, title: "Senior Engineer" } }
  ) {
    id
    properties
  }
}

# Merge (upsert) node
mutation MergePerson {
  mergeNode(
    labels: ["Person"]
    matchProperties: { email: "alice@example.com" }
    setProperties: { lastLogin: "2024-12-16" }
  ) {
    id
    properties
  }
}

# Delete node
mutation DeleteNode {
  deleteNode(id: "node-id")
}

Real-Time Subscriptions

GraphQL subscriptions enable real-time updates when nodes and relationships are created, updated, or deleted. This is perfect for building reactive applications, dashboards, and live collaboration features.

Available Subscriptions

  1. nodeCreated(labels: [String!]) - Subscribe to new nodes (optionally filtered by labels)
  2. nodeUpdated(id: ID, labels: [String!]) - Subscribe to node updates (optionally filtered by ID or labels)
  3. nodeDeleted(labels: [String!]) - Subscribe to node deletions (optionally filtered by labels)
  4. relationshipCreated(types: [String!]) - Subscribe to new relationships (optionally filtered by types)
  5. relationshipUpdated(id: ID, types: [String!]) - Subscribe to relationship updates
  6. relationshipDeleted(types: [String!]) - Subscribe to relationship deletions

Subscription Examples

Subscribe to All New Nodes

subscription AllNewNodes {
  nodeCreated {
    id
    labels
    properties
    createdAt
  }
}

Subscribe to Specific Node Labels

subscription NewPersons {
  nodeCreated(labels: ["Person"]) {
    id
    labels
    properties
  }
}

Subscribe to Node Updates

# All node updates
subscription AllNodeUpdates {
  nodeUpdated {
    id
    labels
    properties
    updatedAt
  }
}

# Updates for a specific node
subscription NodeUpdate($nodeId: ID!) {
  nodeUpdated(id: $nodeId) {
    id
    properties
    updatedAt
  }
}

# Updates for nodes with specific labels
subscription PersonUpdates {
  nodeUpdated(labels: ["Person"]) {
    id
    properties
    updatedAt
  }
}

Subscribe to Node Deletions

subscription DeletedPersons {
  nodeDeleted(labels: ["Person"])
}

Subscribe to Relationship Events

# All new relationships
subscription NewRelationships {
  relationshipCreated {
    id
    type
    startNode {
      id
      labels
    }
    endNode {
      id
      labels
    }
    properties
  }
}

# Specific relationship types
subscription NewKnowsRelationships {
  relationshipCreated(types: ["KNOWS"]) {
    id
    type
    startNode {
      id
    }
    endNode {
      id
    }
  }
}

# Relationship updates
subscription RelationshipUpdates {
  relationshipUpdated {
    id
    type
    properties
    updatedAt
  }
}

# Relationship deletions
subscription DeletedRelationships {
  relationshipDeleted(types: ["KNOWS"])
}

Using Subscriptions in JavaScript/TypeScript

import { createClient } from 'graphql-ws';

// Create WebSocket client
const client = createClient({
  url: 'ws://localhost:7474/graphql',
  connectionParams: {
    Authorization: 'Bearer <your-token>',
  },
});

// Subscribe to new Person nodes
const unsubscribe = client.subscribe(
  {
    query: `
      subscription NewPersons {
        nodeCreated(labels: ["Person"]) {
          id
          labels
          properties
        }
      }
    `,
  },
  {
    next: (data) => {
      console.log('New person created:', data.data.nodeCreated);
    },
    error: (err) => {
      console.error('Subscription error:', err);
    },
    complete: () => {
      console.log('Subscription completed');
    },
  }
);

// Later, unsubscribe
unsubscribe();

Using Subscriptions with Apollo Client

import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';

const httpLink = new HttpLink({
  uri: 'http://localhost:7474/graphql',
  headers: {
    Authorization: 'Bearer <your-token>',
  },
});

const wsLink = new GraphQLWsLink(
  createClient({
    url: 'ws://localhost:7474/graphql',
    connectionParams: {
      Authorization: 'Bearer <your-token>',
    },
  })
);

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

// Use subscription in React component
import { useSubscription } from '@apollo/client';

const NEW_PERSONS_SUBSCRIPTION = gql`
  subscription NewPersons {
    nodeCreated(labels: ["Person"]) {
      id
      labels
      properties
    }
  }
`;

function PersonList() {
  const { data, loading } = useSubscription(NEW_PERSONS_SUBSCRIPTION);

  if (loading) return <p>Loading...</p>;

  return (
    <div>
      <h2>New Person: {data?.nodeCreated?.properties?.name}</h2>
    </div>
  );
}

Using Subscriptions with cURL (Server-Sent Events)

GraphQL subscriptions over HTTP use Server-Sent Events (SSE):

curl -N -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "subscription { nodeCreated(labels: [\"Person\"]) { id labels properties } }"
  }' \
  http://localhost:7474/graphql

Filtering Subscriptions

Subscriptions support flexible filtering:

  • Label filtering: Only receive events for nodes with specific labels
  • Type filtering: Only receive events for relationships of specific types
  • ID filtering: Only receive events for a specific node or relationship

Examples:

# Only Person nodes
subscription Persons {
  nodeCreated(labels: ["Person"]) { id }
}

# Multiple labels (OR logic - matches any)
subscription PeopleOrCompanies {
  nodeCreated(labels: ["Person", "Company"]) { id }
}

# Specific relationship types
subscription KnowsOrLikes {
  relationshipCreated(types: ["KNOWS", "LIKES"]) { id type }
}

# Specific node updates
subscription AliceUpdates {
  nodeUpdated(id: "alice-id") { id properties }
}

Subscription Best Practices

  1. Use filters to reduce unnecessary events and bandwidth
  2. Handle errors gracefully - subscriptions may disconnect
  3. Reconnect logic - implement automatic reconnection for production
  4. Rate limiting - be aware of event volume for high-traffic applications
  5. Cleanup - properly unsubscribe when components unmount

Performance Considerations

  • Subscriptions use buffered channels (size: 10) to prevent blocking
  • Events are dropped if the channel is full (prevents memory issues)
  • Filtering happens server-side for efficiency
  • Multiple subscriptions can run concurrently

Cypher Pass-through

For complex queries, you can execute Cypher directly:

# Execute Cypher queries
query CypherQuery {
  cypher(
    input: {
      statement: "MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN p.name, c.name LIMIT 10"
    }
  ) {
    columns
    rows
    rowCount
  }
}

# Cypher with parameters
query CypherWithParams {
  cypher(
    input: {
      statement: "MATCH (p:Person) WHERE p.age > $minAge RETURN p"
      parameters: { minAge: 25 }
    }
  ) {
    columns
    rows
  }
}

Integration Examples

HTTP API

# Query
curl -X POST http://localhost:7474/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"query": "{ stats { nodeCount } }"}'

# Mutation
curl -X POST http://localhost:7474/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "query": "mutation { createNode(input: { labels: [\"Test\"], properties: {} }) { id } }"
  }'

JavaScript/TypeScript

const response = await fetch("/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <token>",
  },
  body: JSON.stringify({
    query: `
      query GetNode($id: ID!) {
        node(id: $id) {
          id
          labels
          properties
        }
      }
    `,
    variables: { id: "node-123" },
  }),
});

const { data, errors } = await response.json();

Python

import requests

response = requests.post(
    "http://localhost:7474/graphql",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer <token>",
    },
    json={
        "query": """
            query GetNode($id: ID!) {
                node(id: $id) {
                    id
                    labels
                    properties
                }
            }
        """,
        "variables": {"id": "node-123"},
    },
)

data = response.json()

Performance Tips

  1. Batching: Use bulk operations for creating/deleting multiple nodes
  2. Pagination: Always use limit and offset for large result sets
  3. Field Selection: Only request fields you need to minimize data transfer
  4. Cypher Fallback: For complex queries, use the Cypher pass-through for optimal performance
  5. Subscription Filters: Use filters to reduce event volume

Ready to start?GraphQL Playground