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- A JSON object of properties. Each value follows the Cypher property rule (see Property values)DateTime- RFC3339 timestampsFloatArray- 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
}
}maxDepth counts relationships and includes paths exactly at that depth. allPaths(startNodeId: "start-id", endNodeId: "end-id", maxDepth: 5, limit: 10) returns up to ten paths, rather than just a shortest path. Both fields traverse relationships in either direction; shortestPath can restrict relationship types. Nonpositive depth or limit returns an empty result.
Mutations
Property values
The properties, matchProperties and setProperties inputs are written as Cypher properties, so each value must be a property value: a string, a number, a boolean, null, or a list of those. A nested JSON object, or a list that contains lists or objects, is rejected with Neo.ClientError.Statement.TypeError and nothing is written (see Maps are not property values):
mutation {
createNode(input: { labels: ["Person"], properties: { name: "Alice", address: { city: "San Francisco" } } }) {
id
}
}
# -> Neo.ClientError.Statement.TypeErrorTo store structured data, flatten it into properties (address_city), model it as a related node, or store it as a JSON string and parse it in the application.
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
nodeCreated(labels: [String!])- Subscribe to new nodes (optionally filtered by labels)nodeUpdated(id: ID, labels: [String!])- Subscribe to node updates (optionally filtered by ID or labels)nodeDeleted(labels: [String!])- Subscribe to node deletions (optionally filtered by labels)relationshipCreated(types: [String!])- Subscribe to new relationships (optionally filtered by types)relationshipUpdated(id: ID, types: [String!])- Subscribe to relationship updatesrelationshipDeleted(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: "bolt://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: 'bolt://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/graphqlFiltering 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
- Use filters to reduce unnecessary events and bandwidth
- Handle errors gracefully - subscriptions may disconnect
- Reconnect logic - implement automatic reconnection for production
- Rate limiting - be aware of event volume for high-traffic applications
- 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
- Batching: Use bulk operations for creating/deleting multiple nodes
- Pagination: Always use
limitandoffsetfor large result sets - Field Selection: Only request fields you need to minimize data transfer
- Cypher Fallback: For complex queries, use the Cypher pass-through for optimal performance
- Subscription Filters: Use filters to reduce event volume
Related Documentation
- API Reference - Complete function documentation
- Cypher Queries - Cypher language guide
- Vector Search - Semantic search guide
- Complete Examples - Full application examples
Ready to start? → GraphQL Playground