Skip to main content

TypeScript setup

The graph-specific options — labels on an entity, relationship on an entity or a relation property — are not invented by a custom decorator. They are added to MikroORM's own option types by declaration merging, so they appear in autocomplete beside nullable and ref, and no as any is ever needed.

@ManyToOne(() => Author, { ref: true, relationship: { type: 'WROTE', direction: 'IN' } })
// ^ autocompletes, type-checked
author!: Ref<Author>;
Why not a custom @Rel() decorator?

Reusing MikroORM's PropertyOptions keeps the options inside the lifecycle the ORM already runs: discovery, metadata caching, schema generation and the reflection providers all see them. A custom decorator would have to be scanned separately, which is exactly where reflection-based extraction gets brittle.

Which package the decorators come from​

MikroORM 7 moved the classic decorators out of @mikro-orm/core:

import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';

Everything else — Collection, Ref, wrap, the exception classes, defineEntity — comes from mikro-orm-neo4j, which re-exports core.

Property-level options​

The property option lives on interfaces (PropertyOptions, ReferenceOptions), and TypeScript merges interfaces across modules. If your project does not pick the option up — a strict types array in tsconfig.json is the usual reason — one file restores it:

src/mikro-orm-neo4j.d.ts
import '@mikro-orm/core';

declare module '@mikro-orm/core' {
interface PropertyOptions<Owner> {
relationship?: { type?: string; direction?: 'IN' | 'OUT' };
}
}

Entity-level options​

labels and the entity-level relationship sit on EntityOptions, which core declares as a type alias rather than an interface — and a type alias cannot be merged into from another module. Two routes work today, and both are exact:

import { defineEntity } from 'mikro-orm-neo4j';

// 1. defineEntity — the graph options are part of its own signature.
export const MovieSchema = defineEntity({
name: 'Movie',
labels: ['Movie', 'Production'],
properties: (p) => ({ id: p.uuid().primary(), title: p.string() }),
});
import type { EntityOptions } from '@mikro-orm/core';
import { Entity } from '@mikro-orm/decorators/legacy';

// 2. Decorators — assert the options object.
@Entity({ labels: ['Movie', 'Production'] } as EntityOptions<typeof Movie>)
export class Movie {
/* … */
}
note

The assertion is a compiler formality, not a cast away from safety: the driver reads labels off the metadata either way, and the object is still checked against every other field of EntityOptions.

Compiler options​

tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

experimentalDecorators and emitDecoratorMetadata are only needed for the decorator route (with import 'reflect-metadata' at your entry point). defineEntity needs neither, which is one reason to prefer it in a new codebase — the other being that its entity types are inferred rather than reflected.