Complete API documentation for sqlite-graph, a SQLite-based graph database for Node.js.
Main graph database class providing CRUD operations for nodes and edges, fluent query DSL, and graph traversal capabilities.
new GraphDatabase(path: string, options?: DatabaseOptions)Creates a new graph database instance.
Parameters:
path(string): Path to SQLite database file. Use:memory:for in-memory database.options(DatabaseOptions, optional): Database configuration optionsschema(GraphSchema): Optional graph schema for validationreadonly(boolean): Open database in read-only modefileMustExist(boolean): Require database file to existtimeout(number): Busy timeout in millisecondsverbose(function): Function to call for each SQL statement
Throws:
Error: If database file doesn't exist andfileMustExistis trueError: If database initialization fails
Examples:
// In-memory database
const db = new GraphDatabase(':memory:');
// File-based database
const db = new GraphDatabase('./graph.db');
// With schema validation
const db = new GraphDatabase('./graph.db', {
schema: {
nodes: {
Job: { properties: ['title', 'status', 'url'] },
Company: { properties: ['name', 'url'] },
Skill: { properties: ['name', 'category'] }
},
edges: {
POSTED_BY: { from: 'Job', to: 'Company' },
REQUIRES: { from: 'Job', to: 'Skill' }
}
}
});Create a new node in the graph database.
createNode<T extends NodeData = NodeData>(type: string, properties: T): Node<T>Parameters:
type(string): Node type (e.g., 'Job', 'Company', 'Skill')properties(T): Node properties as key-value object
Returns: The created node with assigned ID and timestamps
Throws:
Error: If node type is invalidError: If properties validation fails (when schema is defined)Error: If database insert fails
Example:
const job = db.createNode('Job', {
title: 'Senior Agentic Engineer',
url: 'https://example.com/job/123',
status: 'discovered',
salary: { min: 150000, max: 200000 },
remote: true
});
console.log(job.id); // 1
console.log(job.createdAt); // 2025-10-28T12:00:00.000Z
console.log(job.properties.title); // "Senior Agentic Engineer"Retrieve a node by its ID.
getNode(id: number): Node | nullParameters:
id(number): Node ID
Returns: The node if found, null otherwise
Throws:
Error: If ID is invalid (not a positive integer)
Example:
const node = db.getNode(1);
if (node) {
console.log(node.type); // "Job"
console.log(node.properties.title); // "Senior Agentic Engineer"
}Update node properties. Merges with existing properties.
updateNode(id: number, properties: Partial<NodeData>): NodeParameters:
id(number): Node IDproperties(Partial): Partial properties to update
Returns: The updated node with new updatedAt timestamp
Throws:
Error: If node doesn't existError: If ID is invalid
Example:
const updated = db.updateNode(1, {
status: 'applied',
appliedAt: new Date().toISOString()
});
console.log(updated.properties.status); // "applied"
console.log(updated.updatedAt > updated.createdAt); // trueDelete a node and all connected edges.
deleteNode(id: number): booleanParameters:
id(number): Node ID
Returns: True if node was deleted, false if not found
Throws:
Error: If ID is invalid
Example:
const deleted = db.deleteNode(1);
console.log(deleted ? 'Deleted' : 'Not found');Create an edge (relationship) between two nodes.
createEdge<T extends NodeData = NodeData>(
type: string,
from: number,
to: number,
properties?: T
): Edge<T>Parameters:
type(string): Edge type (e.g., 'POSTED_BY', 'REQUIRES', 'SIMILAR_TO')from(number): Source node IDto(number): Target node IDproperties(T, optional): Edge properties
Returns: The created edge with assigned ID and timestamp
Throws:
Error: If edge type is invalidError: If from/to nodes don't existError: If schema validation fails
Example:
const job = db.createNode('Job', { title: 'Engineer' });
const skill = db.createNode('Skill', { name: 'TypeScript' });
const edge = db.createEdge('REQUIRES', job.id, skill.id, {
level: 'expert',
required: true,
yearsExperience: 5
});
console.log(edge.from); // job.id
console.log(edge.to); // skill.id
console.log(edge.properties.level); // "expert"Retrieve an edge by its ID.
getEdge(id: number): Edge | nullParameters:
id(number): Edge ID
Returns: The edge if found, null otherwise
Example:
const edge = db.getEdge(1);
if (edge) {
console.log(`${edge.from} -> ${edge.to} (${edge.type})`);
}Delete an edge.
deleteEdge(id: number): booleanParameters:
id(number): Edge ID
Returns: True if edge was deleted, false if not found
Example:
const deleted = db.deleteEdge(1);Fluent query builder for nodes with method chaining.
Start a fluent query for nodes of a specific type.
nodes(type: string): NodeQueryParameters:
type(string): Node type to query
Returns: A NodeQuery builder for method chaining
Example:
const activeJobs = db.nodes('Job')
.where({ status: 'active' })
.orderBy('created_at', 'desc')
.limit(10)
.exec();Filter nodes by property values. Can be called multiple times - conditions are ANDed together.
where(properties: Partial<NodeData>): NodeQueryParameters:
properties(Partial): Key-value pairs to match against node properties
Returns: This query builder for chaining
Example:
db.nodes('Job')
.where({ status: 'active' })
.where({ remote: true })
.exec();Filter nodes by a custom predicate function. Executes in JavaScript after fetching from database.
filter(predicate: (node: Node) => boolean): NodeQueryParameters:
predicate(function): Function that returns true for nodes to include
Returns: This query builder for chaining
Example:
db.nodes('Job')
.filter(node => {
const salary = node.properties.salary;
return salary && salary.min >= 150000;
})
.exec();Filter nodes that have a connection to nodes of a specific type.
connectedTo(
nodeType: string,
edgeType: string,
direction?: TraversalDirection
): NodeQueryParameters:
nodeType(string): Type of connected nodes to filter byedgeType(string): Type of edge connecting the nodesdirection(TraversalDirection): Direction of edge traversal ('out', 'in', or 'both')
Returns: This query builder for chaining
Example:
// Find jobs posted by SaaS companies
db.nodes('Job')
.connectedTo('Company', 'POSTED_BY', 'out')
.exec();
// Find companies that posted active jobs
db.nodes('Company')
.connectedTo('Job', 'POSTED_BY', 'in')
.exec();
// Find all nodes connected in either direction
db.nodes('Person')
.connectedTo('Person', 'KNOWS', 'both')
.exec();Filter nodes that do NOT have a connection to nodes of a specific type.
notConnectedTo(nodeType: string, edgeType: string): NodeQueryParameters:
nodeType(string): Type of nodes to exclude connections toedgeType(string): Type of edge to check
Returns: This query builder for chaining
Example:
// Find jobs with no applications yet
db.nodes('Job')
.notConnectedTo('Application', 'APPLIED_TO')
.exec();Order results by a property field.
orderBy(field: string, direction?: 'asc' | 'desc'): NodeQueryParameters:
field(string): Property field to order bydirection('asc' | 'desc'): Sort direction (default: 'asc')
Returns: This query builder for chaining
Example:
// Newest jobs first
db.nodes('Job')
.orderBy('created_at', 'desc')
.exec();
// Alphabetical by title
db.nodes('Job')
.orderBy('title', 'asc')
.exec();Limit the number of results returned.
limit(n: number): NodeQueryParameters:
n(number): Maximum number of nodes to return
Returns: This query builder for chaining
Throws:
Error: If n is not a positive integer
Example:
db.nodes('Job')
.orderBy('created_at', 'desc')
.limit(10)
.exec(); // Returns at most 10 nodesSkip a number of results (for pagination).
offset(n: number): NodeQueryParameters:
n(number): Number of nodes to skip
Returns: This query builder for chaining
Throws:
Error: If n is negative
Example:
// Page 3 of results (20 per page)
db.nodes('Job')
.orderBy('created_at', 'desc')
.limit(20)
.offset(40)
.exec();Execute the query and return all matching nodes.
exec(): Node[]Returns: Array of nodes matching the query
Example:
const results = db.nodes('Job')
.where({ status: 'active' })
.exec();
console.log(`Found ${results.length} active jobs`);Execute the query and return the first matching node.
first(): Node | nullReturns: The first node or null if no matches
Example:
const job = db.nodes('Job')
.where({ id: 123 })
.first();
if (job) {
console.log(job.properties.title);
}Count the number of matching nodes without fetching them.
count(): numberReturns: Number of nodes matching the query
Example:
const count = db.nodes('Job')
.where({ status: 'active' })
.count();
console.log(`${count} active jobs`);Check if any nodes match the query.
exists(): booleanReturns: True if at least one node matches
Example:
const hasActive = db.nodes('Job')
.where({ status: 'active' })
.exists();
if (hasActive) {
console.log('Active jobs available!');
}Graph traversal query builder for exploring relationships.
Start a graph traversal from a specific node.
traverse(startNodeId: number): TraversalQueryParameters:
startNodeId(number): ID of the node to start traversal from
Returns: A TraversalQuery builder for graph operations
Throws:
Error: If start node doesn't exist
Example:
// Find similar jobs up to 2 hops away
const similarJobs = db.traverse(jobId)
.out('SIMILAR_TO')
.maxDepth(2)
.toArray();Traverse outgoing edges of a specific type.
out(edgeType: string, nodeType?: string): TraversalQueryParameters:
edgeType(string): Type of edges to follownodeType(string, optional): Target node type to filter by
Returns: This query builder for chaining
Example:
// Follow SIMILAR_TO edges to Job nodes
db.traverse(jobId)
.out('SIMILAR_TO', 'Job')
.toArray();Traverse incoming edges of a specific type.
in(edgeType: string, nodeType?: string): TraversalQueryParameters:
edgeType(string): Type of edges to follownodeType(string, optional): Source node type to filter by
Returns: This query builder for chaining
Example:
// Find jobs that require this skill
db.traverse(skillId)
.in('REQUIRES', 'Job')
.toArray();Traverse edges in both directions.
both(edgeType: string, nodeType?: string): TraversalQueryParameters:
edgeType(string): Type of edges to follownodeType(string, optional): Node type to filter by
Returns: This query builder for chaining
Example:
// Find all connected nodes via RELATED edge
db.traverse(nodeId)
.both('RELATED')
.toArray();Filter traversed nodes by a predicate function.
filter(predicate: (node: Node) => boolean): TraversalQueryParameters:
predicate(function): Function that returns true for nodes to include
Returns: This query builder for chaining
Example:
db.traverse(jobId)
.out('SIMILAR_TO')
.filter(node => node.properties.status === 'active')
.toArray();Ensure each node appears only once in results.
unique(): TraversalQueryReturns: This query builder for chaining
Example:
// No duplicate nodes in traversal
db.traverse(jobId)
.out('SIMILAR_TO')
.unique()
.toArray();Set maximum traversal depth (number of hops).
maxDepth(depth: number): TraversalQueryParameters:
depth(number): Maximum number of edges to traverse
Returns: This query builder for chaining
Throws:
Error: If depth is negative
Example:
// Find jobs up to 2 hops away
db.traverse(jobId)
.out('SIMILAR_TO')
.maxDepth(2)
.toArray();Set minimum traversal depth (skip nodes too close).
minDepth(depth: number): TraversalQueryParameters:
depth(number): Minimum number of edges from start
Returns: This query builder for chaining
Throws:
Error: If depth is negative
Example:
// Skip immediate neighbors, start at 2 hops
db.traverse(jobId)
.out('SIMILAR_TO')
.minDepth(2)
.toArray();Execute traversal and return all reachable nodes.
toArray(): Node[]Returns: Array of nodes reached during traversal
Example:
const nodes = db.traverse(jobId)
.out('SIMILAR_TO')
.maxDepth(3)
.toArray();
console.log(`Found ${nodes.length} similar jobs`);Execute traversal and return paths (array of node arrays).
toPaths(): Node[][]Returns: Array of paths, where each path is an array of nodes
Example:
const paths = db.traverse(jobId)
.out('SIMILAR_TO')
.maxDepth(2)
.toPaths();
paths.forEach(path => {
console.log('Path:', path.map(n => n.id).join(' -> '));
});Find the shortest path to a target node using BFS.
shortestPath(targetNodeId: number): Node[] | nullParameters:
targetNodeId(number): ID of target node to find path to
Returns: Array of nodes representing the shortest path, or null if no path exists
Example:
const path = db.traverse(job1Id)
.shortestPath(job2Id);
if (path) {
console.log('Path:', path.map(n => n.properties.title).join(' -> '));
}Find paths to a target node with optional constraints.
paths(targetNodeId: number, options?: {
maxPaths?: number;
maxDepth?: number;
}): Node[][]Parameters:
targetNodeId(number): ID of target nodeoptions(object, optional): Path finding configurationmaxPaths(number): Maximum number of paths to returnmaxDepth(number): Maximum depth to search
Returns: Array of paths, where each path is an array of nodes
Example:
// Find all paths
const allPaths = db.traverse(job1Id).paths(job2Id);
// Limit number of paths
const limitedPaths = db.traverse(job1Id).paths(job2Id, { maxPaths: 5 });
// With maxDepth constraint
const shortPaths = db.traverse(job1Id).paths(job2Id, { maxDepth: 3 });Find all paths to a target node (up to a maximum number).
allPaths(targetNodeId: number, maxPaths?: number): Node[][]Parameters:
targetNodeId(number): ID of target nodemaxPaths(number, optional): Maximum number of paths to return (default: 10)
Returns: Array of paths, where each path is an array of nodes
Example:
const paths = db.traverse(job1Id)
.allPaths(job2Id, 5);
console.log(`Found ${paths.length} paths`);Context object provided to transaction callbacks. Allows manual control over transaction lifecycle with commit, rollback, and savepoints.
Execute a function within a transaction. Automatically commits on success or rolls back on error, unless manually controlled.
transaction<T>(fn: (ctx: TransactionContext) => T): TParameters:
fn(function): Function to execute within transaction, receives TransactionContext
Returns: The return value of the transaction function
Throws:
Error: If transaction function throws (after rollback)
Example:
// Automatic commit/rollback
const result = db.transaction((ctx) => {
const job = db.createNode('Job', { title: 'Engineer' });
const company = db.createNode('Company', { name: 'TechCorp' });
db.createEdge('POSTED_BY', job.id, company.id);
return { job, company };
});
// Manual control with savepoints
db.transaction((ctx) => {
const job = db.createNode('Job', { title: 'Test' });
ctx.savepoint('job_created');
try {
db.createEdge('POSTED_BY', job.id, companyId);
} catch (err) {
ctx.rollbackTo('job_created');
}
ctx.commit();
});Manually commit the transaction. After calling this, the transaction is finalized.
commit(): voidThrows:
TransactionAlreadyFinalizedError: If transaction was already committed or rolled back
Example:
db.transaction((ctx) => {
db.createNode('Job', { title: 'Test' });
ctx.commit(); // Manual commit
});Manually rollback the transaction. All changes made in the transaction will be discarded.
rollback(): voidThrows:
TransactionAlreadyFinalizedError: If transaction was already committed or rolled back
Example:
db.transaction((ctx) => {
db.createNode('Job', { title: 'Test' });
if (someCondition) {
ctx.rollback(); // Discard changes
return;
}
});Create a named savepoint within the transaction. Allows partial rollback to this point later.
savepoint(name: string): voidParameters:
name(string): Name for the savepoint
Throws:
Error: If savepoint with this name already exists
Example:
db.transaction((ctx) => {
db.createNode('Job', { title: 'Job 1' });
ctx.savepoint('sp1');
db.createNode('Job', { title: 'Job 2' });
ctx.rollbackTo('sp1'); // Only Job 1 remains
});Rollback to a previously created savepoint. All changes after the savepoint are discarded.
rollbackTo(name: string): voidParameters:
name(string): Name of the savepoint to rollback to
Throws:
Error: If savepoint doesn't exist
Example:
db.transaction((ctx) => {
db.createNode('Job', { title: 'Job 1' });
ctx.savepoint('sp1');
db.createNode('Job', { title: 'Job 2' });
ctx.rollbackTo('sp1'); // Job 2 is discarded
db.createNode('Job', { title: 'Job 3' }); // Can continue
});Release a savepoint, making its changes permanent within the transaction.
releaseSavepoint(name: string): voidParameters:
name(string): Name of the savepoint to release
Throws:
Error: If savepoint doesn't exist
Example:
db.transaction((ctx) => {
db.createNode('Job', { title: 'Job 1' });
ctx.savepoint('sp1');
db.createNode('Job', { title: 'Job 2' });
ctx.releaseSavepoint('sp1'); // Can't rollback to sp1 anymore
});Export the entire graph to a portable format.
export(): GraphExportReturns: Object containing all nodes and edges with metadata
Example:
const data = db.export();
fs.writeFileSync('graph-backup.json', JSON.stringify(data, null, 2));Import graph data from export format. Note: This does not clear existing data.
import(data: GraphExport): voidParameters:
data(GraphExport): Graph export data
Throws:
Error: If import fails
Example:
const data = JSON.parse(fs.readFileSync('graph-backup.json', 'utf8'));
db.import(data);Close the database connection. After calling this, the database instance should not be used.
close(): voidExample:
db.close();Represents a node in the graph database.
interface Node<T extends NodeData = NodeData> {
id: number;
type: string;
properties: T;
createdAt: Date;
updatedAt: Date;
}Represents an edge (relationship) in the graph database.
interface Edge<T extends NodeData = NodeData> {
id: number;
type: string;
from: number;
to: number;
properties?: T;
createdAt: Date;
}Base type for node/edge property data.
interface NodeData {
[key: string]: any;
}Schema definition for the graph database.
interface GraphSchema {
nodes: {
[nodeType: string]: {
properties?: string[];
indexes?: string[];
};
};
edges: {
[edgeType: string]: {
from: string;
to: string;
properties?: string[];
};
};
}Options for database initialization.
interface DatabaseOptions {
schema?: GraphSchema;
readonly?: boolean;
fileMustExist?: boolean;
timeout?: number;
verbose?: (message?: any, ...additionalArgs: any[]) => void;
}Direction for traversing edges.
type TraversalDirection = 'out' | 'in' | 'both';Graph export format.
interface GraphExport {
nodes: Node[];
edges: Edge[];
metadata?: {
version: string;
exportedAt: string;
};
}Thrown when attempting to use a transaction that has already been committed or rolled back.
class TransactionAlreadyFinalizedError extends Error {
name: 'TransactionAlreadyFinalizedError';
}The following errors may be thrown during validation:
- Invalid node type: Node type is empty or contains invalid characters
- Invalid edge type: Edge type is empty or contains invalid characters
- Invalid node ID: Node ID is not a positive integer
- Node not found: Referenced node does not exist
- Schema validation failed: Properties don't match schema requirements
SQLite errors are propagated from better-sqlite3:
- SQLITE_CONSTRAINT: Constraint violation (e.g., foreign key)
- SQLITE_BUSY: Database is locked
- SQLITE_FULL: Database or disk is full
import { GraphDatabase } from 'sqlite-graph';
const db = new GraphDatabase('./jobs.db');
// Create nodes
const job = db.createNode('Job', {
title: 'Senior Agentic Engineer',
url: 'https://example.com/job/123',
status: 'discovered',
salary: { min: 150000, max: 200000 },
remote: true
});
const company = db.createNode('Company', {
name: 'TechCorp',
url: 'https://techcorp.com',
industry: 'SaaS'
});
const skill1 = db.createNode('Skill', {
name: 'TypeScript',
category: 'programming'
});
const skill2 = db.createNode('Skill', {
name: 'React',
category: 'framework'
});
// Create relationships
db.createEdge('POSTED_BY', job.id, company.id);
db.createEdge('REQUIRES', job.id, skill1.id, {
level: 'expert',
required: true
});
db.createEdge('REQUIRES', job.id, skill2.id, {
level: 'intermediate',
required: false
});
// Query: Find all active remote jobs
const remoteJobs = db.nodes('Job')
.where({ status: 'active', remote: true })
.exec();
// Query: Find jobs requiring TypeScript
const tsJobs = db.nodes('Job')
.connectedTo('Skill', 'REQUIRES')
.filter(job => {
// Additional filtering in JavaScript
return job.properties.salary?.min >= 150000;
})
.orderBy('created_at', 'desc')
.limit(20)
.exec();
// Query: Find all skills required by jobs at TechCorp
const techCorpSkills = db.nodes('Skill')
.connectedTo('Job', 'REQUIRES', 'in')
.connectedTo('Company', 'POSTED_BY')
.exec();
// Traversal: Find similar jobs
const similarJobs = db.traverse(job.id)
.out('SIMILAR_TO')
.maxDepth(2)
.unique()
.toArray();
// Transaction: Apply to multiple jobs atomically
db.transaction((ctx) => {
for (const jobId of [1, 2, 3]) {
db.updateNode(jobId, {
status: 'applied',
appliedAt: new Date().toISOString()
});
}
// Auto-commits on success
});
db.close();const db = new GraphDatabase(':memory:');
// Create people
const alice = db.createNode('Person', { name: 'Alice', age: 30 });
const bob = db.createNode('Person', { name: 'Bob', age: 28 });
const charlie = db.createNode('Person', { name: 'Charlie', age: 35 });
// Create relationships
db.createEdge('KNOWS', alice.id, bob.id);
db.createEdge('KNOWS', bob.id, charlie.id);
db.createEdge('KNOWS', alice.id, charlie.id);
// Find all of Alice's connections (both directions)
const aliceConnections = db.nodes('Person')
.connectedTo('Person', 'KNOWS', 'both')
.where({ id: alice.id })
.exec();
// Find shortest path between Alice and Charlie
const path = db.traverse(alice.id)
.shortestPath(charlie.id);
console.log('Path:', path?.map(n => n.properties.name).join(' -> '));
// Find all paths with depth limit
const allPaths = db.traverse(alice.id)
.paths(charlie.id, { maxPaths: 5, maxDepth: 3 });
db.close();- Use prepared statements: The database automatically prepares frequently used statements
- Batch operations: Use transactions for bulk inserts/updates
- Index properly: Define indexes in schema for frequently queried properties
- Use filters wisely: SQL
where()is faster than JavaScriptfilter() - Limit results: Use
limit()to reduce memory usage for large result sets - Close connections: Always call
close()when done
- README.md - Getting started guide
- examples/ - Additional code examples
- better-sqlite3 - Underlying SQLite library