Relationships
@OneToOne and @OneToMany describe edges from a model. Mark one eager and neode loads it in
the same statement as the node.
Eager loading
Ask for the relationship via the with option and the whole thing comes back hydrated, in one Cypher statement.
@NodeEntity()
export class Movie {
@Property({ type: 'string', unique: true, primary: true })
title: string
@OneToMany(() => Person, 'ACTED_IN', { direction: 'out', eager: true })
actors: Person[]
}
const movie = await neode.find(Movie, title, { with: { actors: true } })Relationship entities
When an edge carries properties, model it as an entity with @StartNode and @EndNode. Create
and remove edges with relate() and detach(), and use cascade deletes when a node should take
its edges with it.
Read models
Read-model decorators describe what a read should return. All of them are projected into one query per read, so there is no N+1.
- @RelatedOne
- A single related node
- @RelatedMany
- A list of related nodes
- @RelationshipCount
- How many edges, without loading them
- @RelationshipExists
- Whether an edge exists
- @Computed, @Expression
- Derived values computed in the same statement
See it
import { Direction, NodeEntity, OneToMany, Property, Uuid, fromEnv } from 'neode'
@NodeEntity()
class Pet {
@Uuid({ primary: true })
id!: string
@Property({ type: 'string' })
name!: string
}
@NodeEntity()
class Person {
@Uuid({ primary: true })
id!: string
@Property({ type: 'string' })
name!: string
@OneToMany(() => Pet, 'OWNS', Direction.OUTGOING)
pets!: Pet[]
}
async function main(): Promise<void> {
const neode = fromEnv()
try {
const adam = await neode.create(Person, { name: 'Adam' })
const rex = await neode.create(Pet, { name: 'Rex' })
// MERGE the relationship, with properties
await neode.relate(Person, adam, rex, 'pets', { since: '2024-01-01' })
// Remove just the relationship — both nodes survive
await neode.detach(Person, adam, rex, 'pets')
// Put it back (MERGE, so re-running never duplicates it)
await neode.relate(Person, adam, rex, 'pets')
// Delete the owner. cascade mode 'detach' removes only the
// relationships — Rex stays alive but unlinked; 'delete' (the
// default cascade mode) deletes the related nodes recursively.
await neode.delete(adam, { cascade: true, mode: 'detach' })
const orphan = await neode.getFirst(Pet, { name: 'Rex' })
console.log(`Rex survived the owner: ${orphan !== undefined}`)
await neode.delete(rex)
} finally {
await neode.close()
}
}
main().catch(console.error)