Skip to main content
Preview
Preview Feature — This feature is currently in preview and under active development. APIs and functionality may change. We recommend testing thoroughly before using in production.

Working with Nodes

Learn how to create, query, update, and delete nodes in your graph database. This guide covers all node operations with practical examples and best practices.

Quick Example​

// Create a node
const user = await ductape.graph.createNode({
labels: ['Person', 'User'],
properties: {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 28,
},
});

// Find nodes
const adults = await ductape.graph.findNodes({
labels: ['Person'],
where: { age: { $gte: 18 } },
limit: 10,
});

// Update a node
await ductape.graph.updateNode({
id: user.node.id,
properties: { age: 29, lastLogin: new Date() },
});

Creating Nodes​

Basic Node Creation​

Create a node with labels and properties:

const result = await ductape.graph.createNode({
labels: ['Person'],
properties: {
name: 'Bob Smith',
email: 'bob@example.com',
age: 32,
city: 'New York',
joined: new Date(),
},
});

console.log('Created node ID:', result.node.id);
console.log('Node properties:', result.node.properties);

Multiple Labels​

Nodes can have multiple labels for classification:

const result = await ductape.graph.createNode({
labels: ['Person', 'Employee', 'Engineer'],
properties: {
name: 'Charlie Davis',
email: 'charlie@company.com',
role: 'Senior Engineer',
department: 'Engineering',
salary: 120000,
},
});

Complex Properties​

Nodes support various property types:

const result = await ductape.graph.createNode({
labels: ['Product'],
properties: {
// Strings
name: 'Laptop Pro',
sku: 'LPT-2024-001',

// Numbers
price: 1299.99,
stock: 45,

// Booleans
inStock: true,
featured: false,

// Dates
releaseDate: new Date('2024-01-15'),
lastUpdated: new Date(),

// Arrays
tags: ['electronics', 'computers', 'premium'],
colors: ['silver', 'space gray'],

// Objects (stored as JSON strings in most graph DBs)
specs: {
cpu: 'M3 Pro',
ram: '16GB',
storage: '512GB SSD',
},
},
});

Finding Nodes​

Find All Nodes with Label​

const result = await ductape.graph.findNodes({
labels: ['Person'],
});

console.log('Found nodes:', result.nodes.length);
result.nodes.forEach(node => {
console.log(`${node.properties.name} - ID: ${node.id}`);
});

Find with Filters​

Use the where clause to filter nodes. Ductape uses lowercase operators following the Mongoose/MongoDB convention:

const result = await ductape.graph.findNodes({
labels: ['Person'],
where: {
city: 'San Francisco',
age: { $gt: 25 },
},
limit: 10,
});

Available Filter Operators​

OperatorDescriptionExample
$gtGreater than{ age: { $gt: 18 } }
$gteGreater than or equal{ age: { $gte: 18 } }
$ltLess than{ price: { $lt: 100 } }
$lteLess than or equal{ price: { $lte: 100 } }
$neNot equal{ status: { $ne: 'deleted' } }
$inIn array{ role: { $in: ['admin', 'moderator'] } }
$ninNot in array{ status: { $nin: ['banned', 'suspended'] } }
$containsString contains{ email: { $contains: '@gmail.com' } }
$startsWithString starts with{ name: { $startsWith: 'Dr.' } }
$endsWithString ends with{ email: { $endsWith: '.edu' } }
$existsProperty exists{ verified: { $exists: true } }
$regexRegex match{ email: { $regex: '^user.*@example.com$' } }
Backwards Compatibility

Uppercase operators (e.g., $GT, $IN) are still supported for backwards compatibility, but lowercase is recommended.

Complex Filters​

Combine multiple conditions using array syntax (Mongoose-style):

const result = await ductape.graph.findNodes({
labels: ['Product'],
where: {
$and: [
{ price: { $gte: 50 } },
{ price: { $lte: 500 } },
{ inStock: true },
{ category: { $in: ['electronics', 'gadgets'] } },
],
},
limit: 20,
});

OR Conditions​

const result = await ductape.graph.findNodes({
labels: ['Person'],
where: {
$or: [
{ role: 'admin' },
{ isSuperUser: true },
{ department: 'Security' },
],
},
});

Nested AND/OR​

const result = await ductape.graph.findNodes({
labels: ['Order'],
where: {
$and: [
{ total: { $gt: 100 } },
{ status: { $in: ['pending', 'processing'] } },
{
$or: [
{ priority: 'high' },
{ expressShipping: true },
],
},
],
},
});

Pattern Matching​

// Find users with Gmail addresses
const result = await ductape.graph.findNodes({
labels: ['User'],
where: {
email: { $endsWith: '@gmail.com' },
name: { $startsWith: 'John' },
},
});

Finding Nodes by ID​

Single Node Lookup​

const node = await ductape.graph.findNodeById('node-id-123');

if (node) {
console.log('Found:', node.properties.name);
} else {
console.log('Node not found');
}

With Type Safety​

interface PersonProperties {
name: string;
email: string;
age: number;
}

const person = await ductape.graph.findNodeById<PersonProperties>('node-id-123');

if (person) {
console.log(`${person.properties.name} is ${person.properties.age} years old`);
}

Updating Nodes​

Update Properties​

Replace or add properties to an existing node:

const result = await ductape.graph.updateNode({
id: 'node-id-123',
properties: {
age: 29,
lastLogin: new Date(),
status: 'active',
},
});

console.log('Updated node:', result.node.properties);

Partial Updates​

Only specified properties are updated; others remain unchanged:

// Original node: { name: 'Alice', email: 'alice@example.com', age: 28 }

await ductape.graph.updateNode({
id: nodeId,
properties: { age: 29 },
});

// Result: { name: 'Alice', email: 'alice@example.com', age: 29 }

Update by Filter​

Update multiple nodes matching criteria:

const result = await ductape.graph.updateNode({
labels: ['User'],
where: { lastLogin: { $lt: new Date('2024-01-01') } },
properties: { status: 'inactive' },
});

console.log('Updated nodes:', result.updatedCount);

Increment Values​

// Increment a counter
await ductape.graph.updateNode({
id: nodeId,
properties: {
loginCount: { $INCREMENT: 1 },
reputation: { $INCREMENT: 10 },
},
});

Deleting Nodes​

Delete by ID​

const result = await ductape.graph.deleteNode({
id: 'node-id-123',
detach: true, // Also delete connected relationships
});

console.log('Deleted:', result.deleted);

Delete by Filter​

Delete multiple nodes matching criteria:

const result = await ductape.graph.deleteNode({
labels: ['TempUser'],
where: {
createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }, // Older than 30 days
},
detach: true,
});

console.log('Deleted nodes:', result.deletedCount);

Detach vs Non-Detach​

Detach Delete (recommended):

// Deletes the node AND all its relationships
await ductape.graph.deleteNode({
id: nodeId,
detach: true,
});

Non-Detach Delete:

// Fails if node has relationships (ensures referential integrity)
await ductape.graph.deleteNode({
id: nodeId,
detach: false,
});

Merge Operations​

Merge creates a node if it doesn't exist, or updates it if it does (upsert operation).

Basic Merge​

const result = await ductape.graph.mergeNode({
labels: ['Person'],
matchProperties: { email: 'alice@example.com' },
onCreate: {
name: 'Alice Johnson',
email: 'alice@example.com',
createdAt: new Date(),
},
onMatch: {
lastSeen: new Date(),
},
});

if (result.created) {
console.log('Created new node:', result.node.id);
} else {
console.log('Updated existing node:', result.node.id);
}

Match on Multiple Properties​

const result = await ductape.graph.mergeNode({
labels: ['Product'],
matchProperties: {
sku: 'LPT-2024-001',
vendor: 'TechCorp',
},
onCreate: {
name: 'Laptop Pro',
sku: 'LPT-2024-001',
vendor: 'TechCorp',
price: 1299.99,
stock: 100,
createdAt: new Date(),
},
onMatch: {
stock: { $INCREMENT: 50 },
lastRestocked: new Date(),
},
});

Conditional Properties​

const result = await ductape.graph.mergeNode({
labels: ['User'],
matchProperties: { email: 'bob@example.com' },
onCreate: {
name: 'Bob Smith',
email: 'bob@example.com',
role: 'user',
createdAt: new Date(),
loginCount: 1,
},
onMatch: {
loginCount: { $INCREMENT: 1 },
lastLogin: new Date(),
},
});

Batch Operations​

Create Multiple Nodes​

const people = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' },
];

for (const person of people) {
await ductape.graph.createNode({
labels: ['Person'],
properties: person,
});
}

Efficient Batch with Transaction​

await ductape.graph.executeTransaction(async (tx) => {
for (const person of people) {
await ductape.graph.createNode({
labels: ['Person'],
properties: person,
}, tx);
}
});

Node Structure​

Node Object​

interface INode<T = NodeProperties> {
id: string | number; // Database-specific ID
labels: string[]; // Node labels/types
properties: T; // Node properties
elementId?: string; // Neo4j 5.x element ID
}

Result Types​

Create Result:

interface ICreateNodeResult<T> {
node: INode<T>; // The created node
created: boolean; // Always true for create
}

Find Result:

interface IFindNodesResult<T> {
nodes: INode<T>[]; // Array of matching nodes
count: number; // Total count (for pagination)
}

Update Result:

interface IUpdateNodeResult<T> {
node?: INode<T>; // Updated node (if single update)
nodes?: INode<T>[]; // Updated nodes (if batch update)
updatedCount: number; // Number of nodes updated
}

Delete Result:

interface IDeleteNodeResult {
deleted: boolean; // Whether deletion succeeded
deletedCount: number; // Number of nodes deleted
}

Merge Result:

interface IMergeNodeResult<T> {
node: INode<T>; // The resulting node
created: boolean; // true if created, false if matched
}

Use Case Examples​

User Registration​

async function registerUser(email: string, name: string, password: string) {
// Check if user exists
const existing = await ductape.graph.findNodes({
labels: ['User'],
where: { email },
limit: 1,
});

if (existing.nodes.length > 0) {
throw new Error('User already exists');
}

// Create new user
const user = await ductape.graph.createNode({
labels: ['User'],
properties: {
email,
name,
passwordHash: hashPassword(password),
createdAt: new Date(),
status: 'active',
emailVerified: false,
},
});

return user.node;
}

Product Catalog​

async function addOrUpdateProduct(sku: string, productData: any) {
const result = await ductape.graph.mergeNode({
labels: ['Product'],
matchProperties: { sku },
onCreate: {
...productData,
sku,
createdAt: new Date(),
views: 0,
},
onMatch: {
...productData,
updatedAt: new Date(),
},
});

return result;
}

Activity Tracking​

async function trackUserActivity(userId: string) {
await ductape.graph.updateNode({
id: userId,
properties: {
lastActive: new Date(),
activityCount: { $INCREMENT: 1 },
},
});
}

Soft Delete Pattern​

async function softDeleteUser(userId: string) {
await ductape.graph.updateNode({
id: userId,
properties: {
status: 'deleted',
deletedAt: new Date(),
},
});
}

async function getActiveUsers() {
return ductape.graph.findNodes({
labels: ['User'],
where: {
status: { $ne: 'deleted' },
},
});
}

Best Practices​

1. Use Meaningful Labels​

// Good - descriptive and hierarchical
await ductape.graph.createNode({
labels: ['Person', 'Employee', 'Engineer'],
properties: { name: 'Alice' },
});

// Avoid - too generic
await ductape.graph.createNode({
labels: ['Node'],
properties: { name: 'Alice' },
});

2. Index Frequently Queried Properties​

// Create an index for faster lookups
await ductape.graph.createNodeIndex({
name: 'idx_user_email',
label: 'User',
properties: ['email'],
unique: true,
});

3. Use Merge for Idempotent Operations​

// Merge ensures the operation can be retried safely
const result = await ductape.graph.mergeNode({
labels: ['User'],
matchProperties: { email },
onCreate: userData,
onMatch: { lastSeen: new Date() },
});

4. Always Use Detach When Deleting​

// Prevents orphaned relationships
await ductape.graph.deleteNode({
id: nodeId,
detach: true,
});

5. Validate Properties Before Creation​

function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

async function createUser(data: any) {
if (!validateEmail(data.email)) {
throw new Error('Invalid email');
}

return ductape.graph.createNode({
labels: ['User'],
properties: data,
});
}

Next Steps​

See Also​