Skip to content
neode 1.0 alpha

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)
← Defining models Traversals →