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
| Operator | Description | Example |
|---|---|---|
$gt | Greater than | { age: { $gt: 18 } } |
$gte | Greater than or equal | { age: { $gte: 18 } } |
$lt | Less than | { price: { $lt: 100 } } |
$lte | Less than or equal | { price: { $lte: 100 } } |
$ne | Not equal | { status: { $ne: 'deleted' } } |
$in | In array | { role: { $in: ['admin', 'moderator'] } } |
$nin | Not in array | { status: { $nin: ['banned', 'suspended'] } } |
$contains | String contains | { email: { $contains: '@gmail.com' } } |
$startsWith | String starts with | { name: { $startsWith: 'Dr.' } } |
$endsWith | String ends with | { email: { $endsWith: '.edu' } } |
$exists | Property exists | { verified: { $exists: true } } |
$regex | Regex match | { email: { $regex: '^user.*@example.com$' } } |
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
- Manage Relationships - Connect nodes with relationships
- Traverse Graphs - Find paths and explore neighborhoods
- Advanced Querying - Complex patterns and full-text search
- Use Transactions - Ensure data consistency
See Also
- Graph Overview - Full API reference
- Best Practices - Performance optimization