Getting Started
This guide walks you through creating your first graph database, querying relationships, adding the indexes introduced in NeuG v0.2, and exploring both embedded and service modes. Examples are in Python.
Using another language? See the Node.js API reference or C++ API reference for equivalent examples in those languages.
Prerequisites
Before you begin, make sure you have NeuG installed. If you haven’t installed it yet, please follow the installation guide.
Database Storage Modes
Persistent Database
- Use case: Production applications, data analysis, long-term storage
- Durability: Data survives application restarts
# Persistent mode examples
# Make sure that the database directory exists and is writable by the user.
db_persistent = neug.Database("/path/to/database")In-Memory Database
- Use case: Temporary computations, testing, prototyping
- Durability: Data is lost when the database is closed
# Memory mode examples
db_memory = neug.Database("")Note: Currently, NeuG’s in-memory mode creates a temporary database directory that is automatically cleaned up when the database is closed.
Connection Modes
NeuG provides two modes to access your database:
Embedded Mode
Direct in-process access - easiest for single-user scenarios:
import neug
# Create database and connect directly
db = neug.Database("/path/to/database") # or "" for in-memory
conn = db.connect()
print("Connect to NeuG in embedded mode")
conn.close()
db.close()Service Mode
Network-based access - ideal for multi-user applications:
Start the service:
import neug
# Start NeuG as a service
db = neug.Database("/path/to/database")
service = db.serve(host="localhost", port=10000, blocking=False, thread_num=0)thread_num controls the number of service threads.
The default 0 auto-selects from the database max_thread_num. With the
default database thread setting, max_thread_num is resolved from hardware
concurrency and falls back to 1 if the runtime cannot detect it.
Service threads run TP queries concurrently, but each query uses one execution context and one thread.
Embedded (AP) queries are currently single-threaded; using max_thread_num for intra-query parallelism is future work.
Connect from client:
from neug import Session
# Connect to the service
session = Session("http://localhost:10000/")
session.close()Basic Operations
The following operations work the same way regardless of which database mode (in-memory or persistent) and connection mode (embedded or service) you choose. Let’s assume that we are using a persistent database in embedded mode for this example.
import neug
# Create database and establish connection
db = neug.Database("/path/to/database")
conn = db.connect()Creating Nodes and Edges
Before inserting data, you need to define your graph schema with node and edge types:
# Create node tables
conn.execute("""
CREATE NODE TABLE Person(
id INT64,
name STRING,
age INT64,
email STRING,
bio STRING,
embedding FLOAT[4],
PRIMARY KEY (id)
)
""")
conn.execute("""
CREATE NODE TABLE Company(
id INT64,
name STRING,
industry STRING,
founded_year INT64,
PRIMARY KEY (id)
)
""")
# Create edge tables
conn.execute("""
CREATE REL TABLE WORKS_FOR(
FROM Person TO Company,
position STRING,
start_date DATE,
salary DOUBLE
)
""")
conn.execute("""
CREATE REL TABLE KNOWS(
FROM Person TO Person,
since_year INT64,
relationship_type STRING
)
""")
print("Graph schema created successfully!")Inserting Data
Now let’s add some data to our graph:
# Insert nodes
conn.execute("""
CREATE (p:Person {
id: 1,
name: 'Alice Johnson',
age: 30,
email: '[email protected]',
bio: 'Graph databases and distributed systems',
embedding: [0.1, 0.2, 0.3, 0.4]
})
""")
conn.execute("""
CREATE (p:Person {
id: 2,
name: 'Bob Smith',
age: 35,
email: '[email protected]',
bio: 'Machine learning and semantic search',
embedding: [0.2, 0.1, 0.1, 0.1]
})
""")
conn.execute("""
CREATE (c:Company {id: 1, name: 'TechCorp', industry: 'Technology', founded_year: 2010})
""")
# Insert relationships
conn.execute("""
MATCH (p:Person), (c:Company) WHERE p.id = 1 AND c.id = 1
CREATE (p)-[:WORKS_FOR {position: 'Software Engineer', start_date: date('2020-01-15'), salary: 75000.0}]->(c)
""")
conn.execute("""
MATCH (p1:Person {id: 2}), (p2:Person {id: 1})
CREATE (p1)-[:KNOWS {since_year: 2018, relationship_type: 'colleague'}]->(p2)
""")
print("Data inserted successfully!")Querying Data
Let’s explore your graph with some queries:
# Simple node query
result = conn.execute("MATCH (p:Person) RETURN p.name, p.age")
for record in result:
print(record)
# ['Alice Johnson', 30]
# ['Bob Smith', 35]
# Relationship query
result = conn.execute("""
MATCH (p:Person)-[w:WORKS_FOR]->(c:Company)
RETURN p.name, w.position, c.name
""")
for record in result:
print(f"{record[0]} works as {record[1]} at {record[2]}")
# Alice Johnson works as Software Engineer at TechCorp
# Complex pattern query
result = conn.execute("""
MATCH (p1:Person)-[:KNOWS]->(p2:Person)-[:WORKS_FOR]->(c:Company)
RETURN p1.name as person1, p2.name as person2, c.name as company
""")
for record in result:
print(f"{record[0]} knows {record[1]} who works at {record[2]}")
# Bob Smith knows Alice Johnson who works at TechCorpIndexing the Same Data (NeuG v0.2+)
NeuG v0.2 introduces storage indexes, HNSW vector search, and BM25 full-text search. These capabilities are not available in NeuG v0.1.x. The following examples add semantic and keyword indexes to the same Person nodes used by the graph queries above.
# Load the index extensions
conn.execute("LOAD vector_search;")
conn.execute("LOAD fts;")
# Index semantic embeddings with HNSW
conn.execute("""
CREATE INDEX person_embedding_idx IF NOT EXISTS
ON Person
USING HNSW (embedding)
WITH (metric = 'l2')
""")
# Index biography text for BM25 retrieval
conn.execute("""
CREATE INDEX person_bio_idx IF NOT EXISTS
ON Person
USING FTS (bio)
""")
# Query semantic similarity
semantic_results = conn.execute("""
MATCH (p:Person)
RETURN p.name,
vector_distance_l2(p.embedding, [0.1, 0.2, 0.3, 0.4]) AS distance
ORDER BY distance ASC
LIMIT 5
""")
# Query exact keywords and rank with BM25
keyword_results = conn.execute("""
MATCH (p:Person)
RETURN p.name, bm25(p.bio, 'graph databases') AS score
ORDER BY score ASC
LIMIT 5
""")
print(list(semantic_results))
print(list(keyword_results))
print(list(conn.execute("CALL SHOW_INDEXES() RETURN *;")))The graph structure and both storage indexes are maintained over the same data. Inserts, updates, and deletes update the indexes as part of the same transaction. For index lifecycle and recovery guarantees, see Storage Indexes. For complete search options, see Vector Search and Full-Text Search.
Converting Results to Apache Arrow
NeuG supports converting query results directly to Apache Arrow tables via the to_arrow() method. This enables zero-copy interoperability with the PyData ecosystem — pandas, Polars, DuckDB, and any other library that understands Arrow.
import neug
db = neug.Database("/path/to/database")
conn = db.connect()
# Run a query and convert to Arrow table
result = conn.execute("""
MATCH (p:Person)-[w:WORKS_FOR]->(c:Company)
RETURN p.name AS name, p.age AS age, w.salary AS salary, c.name AS company
""")
arrow_table = result.to_arrow()
print(arrow_table)
# pyarrow.Table
# name: string
# age: int64
# salary: double
# company: string
# Convert to a pandas DataFrame
df = arrow_table.to_pandas()
print(df)
# name age salary company
# 0 Alice Johnson 30 75000.0 TechCorpNote:
to_arrow()requires PyArrow to be installed (pip install pyarrow). The returnedpyarrow.Tablepreserves column names from theRETURNclause of your Cypher query.
Close the connection and database
conn.close()
db.close()Using builtin dataset
NeuG provides several builtin datasets that you can use to quickly get started with graph analysis, learning, or testing. These datasets are ready-to-use and require no setup.
Available Datasets
You can list all available builtin datasets:
from neug.datasets import get_available_datasets
# List all available datasets
datasets = get_available_datasets()
for dataset in datasets:
print(f"{dataset.name}: {dataset.description}")Loading Builtin Datasets
import neug
# Open/create a database
db = neug.Database("/path/to/database")
# Load a builtin dataset into it
db.load_builtin_dataset(dataset_name="modern_graph")Note: Loading a builtin dataset into an existing database will fail if there are schema conflicts (e.g., existing node/edge types with the same names).
Next Steps
Congratulations! You’ve learned the basics of NeuG. Here’s what you can explore next:
- Storage Indexes: Understand index lifecycle, transactions, and recovery
- Vector Search: Configure HNSW indexes and similarity queries
- Full-Text Search: Use BM25, phrase, prefix, and Boolean queries
- Graph Algorithms: Run analytics over projected graphs
- Transaction Management: Learn NeuG’s transaction and isolation model
- Data Import/Export and Cypher Manual: Load production data and write advanced queries