TypeScript ORM

TypeORM

TypeORM bildet TypeScript-Klassen mithilfe von Decorators auf Datenbanktabellen ab. Es unterstützt sowohl das Data Mapper- als auch das Active Record-Pattern und liefert Repositories, einen QueryBuilder und Migrationen in einem Paket.

intermediate15 min readUpdated 16. Sept. 2026
user.entity.ts
ts
// user.entity.ts
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  OneToMany,
  CreateDateColumn,
} from "typeorm";
import { Post } from "./post.entity";

@Entity("users")
export class User {
  @PrimaryGeneratedColumn("uuid")
  id!: string;

  @Column({ unique: true })
  email!: string;

  @Column({ name: "display_name" })
  displayName!: string;

  @CreateDateColumn({ name: "created_at" })
  createdAt!: Date;

  @OneToMany(() => Post, (post) => post.author)
  posts!: Post[];
}
Veröffentlicht
2016
Sprache
TypeScript / JavaScript
Patterns
Data Mapper und Active Record
Datenbanken
Postgres, MySQL, SQLite, MSSQL, Oracle
Stil
Decorator-basierte Entities
Aktuelle Version
0.3.x

Warum es wichtig ist

Was TypeORM in ein TypeScript-Backend bringt

Decorator-gesteuerte Entities

Klassen- und Property-Decorators beschreiben Tabellen und Spalten, sodass das Schema in typisiertem Code lebt, den der Compiler prüfen kann.

First-Class Relationen

@ManyToOne, @OneToMany und @ManyToMany bilden Fremdschlüssel und Join-Tabellen ab, mit eager oder explizitem Laden nach deinen Vorstellungen.

Migrationen aus Entities

Generiere Migrationsdateien durch den Vergleich von Entities mit der Datenbank und führe sie dann in jeder Umgebung nacheinander aus.

Das Gesamtbild

Drei Konzepte, die jede TypeORM-App prägen

Entities beschreiben Tabellen, eine DataSource verwaltet die Verbindung, und Repositories oder der QueryBuilder lesen und schreiben Zeilen.

Entities

Model

Eine dekorierte Klasse wird auf eine Tabelle abgebildet, und jede dekorierte Eigenschaft auf eine Spalte oder eine Relation.

DataSource

Connect

Ein Objekt hält die Verbindungsoptionen und erstellt Entity-Manager, Repositories und Transaktionen.

Repositories

Query

Typisierte Repositories bieten find, findOne und save, während der QueryBuilder alles abdeckt, was diese nicht ausdrücken können.

HTML5 auf einen Blick

Das TypeORM-Toolkit

Column Decorators

@Column, @PrimaryGeneratedColumn, @CreateDateColumn und andere definieren die Tabellenstruktur.

Relationen

@ManyToOne, @OneToMany, @OneToOne und @ManyToMany verknüpfen Tabellen miteinander.

Repository API

find, findOne, save, remove, count und findAndCount mit typisierten where-Klauseln.

QueryBuilder

Kette select, join, where und orderBy aneinander oder wechsle zu raw SQL, wenn du es benötigst.

Migrationen

migration:generate schreibt SQL aus Entity-Diffs; migration:run wendet diese an.

Transaktionen

dataSource.transaction und QueryRunner kapseln mehrstufige Schreibvorgänge atomar.

Datenmodell

Ein Benutzer und seine Posts

Die User-Entity besitzt viele Post-Zeilen, und jeder Post verweist über einen Fremdschlüssel zurück auf einen Autor.

Die Tabellen users und postsTypeORM entities
  • iduuidGenerierter Primärschlüssel, in TypeScript als String dargestellt
  • emailtextEindeutige Spalte, erzwungen durch einen Datenbank-Constraint
  • displayNametextAbgebildet auf die Spalte display_name
  • createdAttimestamptzAutomatisch gesetzt durch @CreateDateColumn
  • postsPost[]One-to-many Relation, wird nur geladen, wenn explizit angefordert

Die User-Entity besitzt viele Post-Zeilen, und jeder Post verweist über einen Fremdschlüssel zurück auf einen Autor.

Eine kurze Geschichte

Von Decorators zur DataSource

  1. 2016

    TypeORM wird veröffentlicht

    Ein Decorator-basiertes ORM für TypeScript erscheint mit dem Ziel, sich in einer typisierten Codebasis natürlich anzufühlen.

    16
  2. 2018

    Die NestJS-Jahre

    @nestjs/typeorm macht TypeORM zur Standard-Datenschicht für eine Generation von Nest-Anwendungen.

    18
  3. 2020

    Migrationen und Subscriber reifen aus

    Schema-Diffing, Entity-Subscriber und Lifecycle-Listener vervollständigen das Framework.

    20
  4. 2022

    Version 0.3 und die DataSource

    createConnection wird durch eine explizite DataSource ersetzt, die die Verbindung und deren Optionen verwaltet.

    22
  5. 2024

    Ein reifes, weit verbreitetes ORM

    TypeORM bleibt in NestJS-Backends weit verbreitet, auch wenn Drizzle und Prisma neue Projekte anziehen.

    24

Der vollständige Leitfaden

TypeORM: Alles was Sie wissen müssen

Was ist TypeORM?

TypeORM ist ein Object-Relational Mapper für TypeScript und JavaScript. Es ermöglicht Ihnen, Ihre Datenbank als eine Menge von Klassen zu beschreiben, die mit Metadaten versehen sind, und diese anschließend über Repositories oder einen Fluent Builder abzufragen. TypeORM existiert bereits seit 2016 und ist das ORM, das am stärksten mit NestJS assoziiert wird.

Das besondere Merkmal ist, dass es zwei bekannte Patterns gleichzeitig unterstützt. Bei Active Record weiß eine Entity, wie sie sich selbst laden und speichern kann. Bei Data Mapper sind Entities einfache Objekte und ein separates Repository ist für die Persistenz zuständig. Die meisten Teams nutzen Data Mapper für den Anwendungscode und gelegentlich Active Record für kleine, in sich geschlossene Modelle.

Wenn Sie bereits SQL beherrschen, lässt sich TypeORM am besten als Mapping-Layer verstehen: Entities werden zu Tabellen, Decorators zu Spaltendefinitionen und jede Abfrage wird letztendlich zu SQL, das Sie auch händisch hätten schreiben können.

Entities: Klassen, die zu Tabellen werden

Eine Entity ist eine Klasse, die mit @Entity() markiert ist. Jede Instanz entspricht einer Zeile und jede dekorierte Eigenschaft einer Spalte.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity("users")
export class User {
  @PrimaryGeneratedColumn("uuid")
  id!: string;

  @Column({ unique: true })
  email!: string;

  @Column({ name: "display_name" })
  displayName!: string;
}

Das Argument für @Entity ist der Tabellenname. Wenn Sie dieses weglassen, leitet TypeORM einen Namen aus dem Klassennamen ab; eine explizite Angabe vermeidet jedoch Überraschungen bei einem Refactoring der Namen. Die name-Option in @Column entkoppelt die Datenbankspalte von der TypeScript-Eigenschaft, wodurch displayName auf display_name gemappt wird.

Das ! nach jeder Eigenschaft ist die sogenannte Definite Assignment Assertion. TypeScript kann nicht erkennen, dass das ORM diese Felder zur Laufzeit befüllt, daher weist ! den Compiler an, der Datenbank zu vertrauen.

Spalten-Dekoratoren, die Sie tatsächlich verwenden werden

TypeORM leitet den Spaltentyp aus dem TypeScript-Typ der Eigenschaft ab, aber diese Inferenz ist begrenzt. Ein string könnte text, varchar oder char sein; für präzise Definitionen sollten Sie den Typ daher explizit übergeben.

@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column({ type: "text" })
  body!: string;

  @Column({ type: "integer", default: 0 })
  views!: number;

  @Column({ type: "boolean", default: false })
  published!: boolean;

  @CreateDateColumn({ name: "created_at" })
  createdAt!: Date;

  @UpdateDateColumn({ name: "updated_at" })
  updatedAt!: Date;
}

Die generated-column-Dekoratoren sind es wert, auswendig gelernt zu werden: @CreateDateColumn setzt einen Zeitstempel beim Einfügen, @UpdateDateColumn aktualisiert diesen bei jedem Update und @DeleteDateColumn ermöglicht Soft Deletes. @PrimaryGeneratedColumn("uuid") erzeugt eine UUID anstelle einer automatisch inkrementierten Ganzzahl, was nützlich ist, wenn IDs öffentlich sichtbar sind.

Speichern Sie Geldbeträge als Ganzzahlen in kleinsten Einheiten in einer integer- oder bigint-Spalte und niemals als Float. Für wirklich optionale Attribute ist eine jsonb-Spalte mit { type: "jsonb" } in Ordnung, aber alles, wonach Sie filtern oder was Sie einschränken möchten, gehört in eine eigene Spalte.

Die DataSource und Konfiguration

Seit Version 0.3 ist eine DataSource das zentrale Objekt, das die Verbindungsoptionen verwaltet und Repositories, Manager sowie Transaktionen bereitstellt.

import "reflect-metadata";
import { DataSource } from "typeorm";

export const AppDataSource = new DataSource({
  type: "postgres",
  url: process.env.DATABASE_URL,
  entities: ["src/entities/*.entity.ts"],
  migrations: ["src/migrations/*.ts"],
  synchronize: false,
  logging: ["error", "warn"],
});

Der reflect-metadata-Import muss erfolgen, bevor eine Entity geladen wird, da die Decorators darauf angewiesen sind. Verweisen Sie in einem kompilierten Build bei entities und migrations auf die generierten .js-Dateien anstelle der .ts-Quelldateien.

Deaktivieren Sie synchronize überall, außer bei einer temporären lokalen Datenbank. Diese Option schreibt das Schema beim Start so um, dass es Ihren Entities entspricht. Das bedeutet, dass eine umbenannte Eigenschaft stillschweigend eine Spalte und deren Daten löschen kann. Migrationen existieren genau deshalb, damit Schemaänderungen überprüft, versioniert und reproduzierbar sind.

Repositories und die find API

Ein Repository ist ein typisiertes Gateway zu einer einzelnen Entität. Du erhältst eines vom DataSource und nutzt es für den Großteil deiner Lese- und Schreibzugriffe.

const users = AppDataSource.getRepository(User);

const user = await users.findOneBy({ id });
const recent = await users.find({
  where: { email: ILike("%@example.com") },
  order: { createdAt: "DESC" },
  take: 20,
  skip: 0,
});

find gibt ein Array zurück und findOne gibt eine einzelne Zeile oder null zurück. Das Options-Objekt akzeptiert where, order, take, skip, select, relations und withDeleted. Operatoren wie ILike, In, MoreThan und IsNull befinden sich im typeorm-Package und werden innerhalb von where zusammengesetzt.

Schreibvorgänge nutzen create, save und remove:

const user = users.create({ email, displayName });
await users.save(user);

user.displayName = "Ada Lovelace";
await users.save(user);

save führt einen Upsert durch: Es fügt einen Datensatz ein, wenn der Primary Key fehlt, und aktualisiert ihn, wenn er vorhanden ist. Diese Komfortfunktion verbirgt, ob eine Zeile erstellt oder geändert wurde. Wenn diese Unterscheidung wichtig ist, verwende insert und update direkt.

Relationen und deren Laden

Relationen werden auf beiden Seiten definiert. Die @ManyToOne-Seite besitzt den Foreign Key, und @OneToMany ist die entsprechende Inverse.

@Entity("posts")
export class Post {
  @ManyToOne(() => User, (user) => user.posts, { onDelete: "CASCADE" })
  @JoinColumn({ name: "author_id" })
  author!: User;

  @Column({ name: "author_id" })
  authorId!: string;
}

@Entity("users")
export class User {
  @OneToMany(() => Post, (post) => post.author)
  posts!: Post[];
}

Es ist ein gängiges und nützliches Pattern, eine explizite authorId-Spalte neben der Relation zu führen: So können Sie den Foreign Key lesen und danach filtern, ohne die verknüpfte Zeile laden zu müssen.

@ManyToMany benötigt eine Join-Tabelle, die auf der besitzenden Seite mit @JoinTable definiert wird. @OneToOne funktioniert wie @ManyToOne, erzwingt jedoch die Eindeutigkeit.

Das Laden ist der entscheidende Teil. Standardmäßig werden Relationen nicht geladen. Sie müssen dies über die relations-Option, ein Eager-Flag oder einen Join im QueryBuilder anfordern.

const posts = await AppDataSource.getRepository(Post).find({
  relations: { author: true },
  where: { published: true },
});

Eager-Relationen werden bei jedem find automatisch geladen, während Lazy-Relationen ein Promise zurückgeben, das bei Zugriff eine Abfrage auslöst. Beides ist komfortabel, verbirgt jedoch die Anzahl der ausgeführten Queries. Bevorzugen Sie das explizite Laden, insbesondere in performance-kritischen Bereichen (Hot Paths), damit die Kosten einer Abfrage dort sichtbar sind, wo sie geschrieben wird.

Der QueryBuilder für echte Abfragen

Der QueryBuilder deckt Joins, Aggregate und dynamische Filter ab, die über die find API nicht sauber ausgedrückt werden können.

const posts = await AppDataSource.getRepository(Post)
  .createQueryBuilder("post")
  .innerJoinAndSelect("post.author", "author")
  .where("author.email = :email", { email })
  .andWhere("post.createdAt >= :since", { since })
  .orderBy("post.createdAt", "DESC")
  .take(20)
  .getMany();

Aliase sind obligatorisch und Parameter werden immer mit :name-Platzhaltern gebunden, niemals per String-Interpolation. getMany gibt Entities zurück, getRawMany gibt einfache Zeilen (plain rows) zurück, und getOne oder getManyAndCount decken die gängigen Fälle für Einzelzeilen und Pagination ab.

Dynamische Filter lassen sich natürlich aufbauen:

const qb = repo.createQueryBuilder("post").where("post.published = true");

if (tag) {
  qb.andWhere("post.tags @> :tag", { tag: [tag] });
}
if (search) {
  qb.andWhere("post.title ILIKE :search", { search: `%${search}%` });
}

Wenn eine Abfrage zu spezialisiert für den Builder ist, führt dataSource.query() parametrisiertes SQL direkt aus und gibt Raw Rows zurück.

Migrationen: Generieren und Ausführen

Migrationen sind der einzige sichere Weg, um ein Production-Schema zu ändern. TypeORM kann diese generieren, indem die Entities mit der Live-Datenbank verglichen werden.

npx typeorm migration:generate src/migrations/CreateUsers -d src/data-source.ts
npx typeorm migration:run -d src/data-source.ts
npx typeorm migration:revert -d src/data-source.ts

Die generierte Datei enthält die Methoden up und down. Lesen Sie diese vor dem Commit sorgfältig durch: Der Diff ist eine bestmögliche Schätzung und erzeugt gelegentlich destruktive Statements, die so nicht beabsichtigt waren.

import { MigrationInterface, QueryRunner } from "typeorm";

export class CreateUsers1710000000000 implements MigrationInterface {
  async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE users (
        id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
        email        text NOT NULL UNIQUE,
        display_name text NOT NULL,
        created_at   timestamptz NOT NULL DEFAULT now()
      )
    `);
  }

  async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE users`);
  }
}

Führen Sie Migrationen als separaten Deploy-Schritt mit einer Rolle aus, die DDL ausführen darf – getrennt von der Rolle, die die Anwendung zur Laufzeit verwendet. Lassen Sie die Anwendung niemals ihr eigenes Schema beim Booten ändern.

Transaktionen

Eine Transaktion gruppiert Statements, sodass entweder alle erfolgreich abgeschlossen werden oder alle zurückgerollt (Rollback) werden. Der DataSource.transaction-Helper übernimmt das Commit, den Rollback und die Verbindung für Sie.

await AppDataSource.transaction(async (manager) => {
  const user = await manager.save(User, { email, displayName });
  await manager.save(Post, { title, body, author: user });
});

Wenn der Callback einen Fehler wirft, führt TypeORM einen Rollback durch und wirft den Fehler erneut. Verwenden Sie für jedes Statement innerhalb des Blocks den bereitgestellten manager; ein Repository, das direkt über den DataSource bezogen wurde, nutzt eine andere Verbindung und würde außerhalb der Transaktion ausgeführt werden.

Für eine präzisere Steuerung bietet createQueryRunner Zugriff auf startTransaction, commitTransaction und rollbackTransaction. Dies ist nützlich, wenn die Transaktion Code umfasst, der nicht als ein einziger Callback ausgedrückt werden kann. Halten Sie solche Transaktionen jedoch kurz: Eine offene Transaktion über einen HTTP-Aufruf hinweg blockiert den Vacuum-Prozess und führt zu Lock Contention.

Das N+1-Problem vermeiden

Das N+1-Problem ist der häufigste Performance-Bug in ORM-Code. Man lädt eine Liste von Posts, iteriert dann darüber und greift auf post.author zu, was eine Abfrage für die Liste plus eine Abfrage pro Post erzeugt.

// N+1: one query for posts, then one per author.
const posts = await repo.find();
for (const post of posts) {
  console.log(post.author.email);
}

Die Lösung besteht darin, die Relation vorab in einer einzigen Abfrage zu laden:

const posts = await repo.find({ relations: { author: true } });

Wenn Sie nur eine Anzahl benötigen, vermeiden Sie es komplett, die Child-Objekte zu laden:

const users = await AppDataSource.getRepository(User)
  .createQueryBuilder("user")
  .loadRelationCountAndMap("user.postCount", "user.posts")
  .getMany();

Aktivieren Sie im Development-Modus das Query-Logging und lesen Sie das Log nach dem Rendern einer Seite. Wenn ein Request Dutzende von nahezu identischen Abfragen auslöst, liegt ein N+1-Problem vor – die relations-Option oder ein Join ist hier die Lösung.

TypeORM in NestJS

@nestjs/typeorm integriert das ORM in die Dependency Injection von Nest. Das Root-Modul verwaltet die Verbindung, während Feature-Module die entsprechenden Repositories anfordern.

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "postgres",
      url: process.env.DATABASE_URL,
      autoLoadEntities: true,
      synchronize: false,
    }),
    TypeOrmModule.forFeature([User, Post]),
  ],
})
export class AppModule {}

Ein Service erhält sein Repository anschließend über den Konstruktor:

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly users: Repository<User>,
  ) {}

  findByEmail(email: string) {
    return this.users.findOneBy({ email });
  }
}

autoLoadEntities registriert Entities, die an forFeature übergeben wurden, wodurch die Root-Konfiguration frei von langen Import-Listen bleibt. Führen Sie Migrationen über die Nest CLI oder ein eigenständiges Skript aus, anstatt sie direkt beim Anwendungsstart zu starten.

Active Record oder Data Mapper?

Bei der Entscheidung geht es primär darum, wo die Persistenz angesiedelt ist. Active Record ist bei einfachen Modellen kürzer, da die Entity ihre eigenen Query-Methoden mitführt.

@Entity("users")
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  email!: string;
}

const user = await User.findOneBy({ id });

Data Mapper hingegen behandelt Entities als reine Datenobjekte und verschiebt die gesamte Persistenz in Repositories:

const users = AppDataSource.getRepository(User);
const user = await users.findOneBy({ id });

Active Record ist in Skripten und kleinen Services praktisch, koppelt das Modell jedoch stark an das ORM und erschwert das Testen. Data Mapper ist die bessere Standardwahl für Anwendungscode und wird auch von NestJS empfohlen. Entscheiden Sie sich pro Projekt für einen Stil, anstatt beide zu mischen, da Konsistenz wichtiger ist als die spezifische Wahl.

Best Practices

  • Lassen Sie synchronize deaktiviert und verwalten Sie jede Schema-Änderung über eine geprüfte Migration.
  • Laden Sie Relationen explizit mit relations oder einem QueryBuilder-Join und protokollieren Sie Queries in der Entwicklungsumgebung.
  • Verwenden Sie @Column({ type: ... }) für alles, bei dem der abgeleitete Typ falsch sein könnte.
  • Behalten Sie die authorId Foreign-Key-Spalte neben der Relation für effizientes Filtern bei.
  • Bevorzugen Sie Data Mapper Repositories für den Anwendungscode und reservieren Sie Active Record für Skripte.
  • Verwenden Sie dataSource.transaction und übergeben Sie die Transaction manager immer an verschachtelte Aufrufe.
  • Binden Sie jeden Wert als Parameter ein; interpolieren Sie niemals Benutzereingaben direkt in SQL.
  • Fügen Sie Indizes für die Spalten hinzu, über die Sie filtern oder Joins durchführen, und bestätigen Sie diese mit EXPLAIN ANALYZE.
  • Führen Sie Migrationen mit einer dedizierten DDL-Rolle aus, die von der Runtime-Rolle der Anwendung getrennt ist.

Häufige Fehler

  • synchronize: true auf einer gemeinsam genutzten Datenbank oder einer Produktionsdatenbank aktiviert lassen.
  • Zugriff auf eine lazy relation innerhalb einer Schleife, was zu einem N+1 Query-Storm führt.
  • Den reflect-metadata-Import vergessen, wodurch die Decorator-Metadaten zur Laufzeit nicht mehr funktionieren.
  • Verwendung des DataSource-Repositorys innerhalb einer Transaction anstelle des bereitgestellten Managers.
  • Die Annahme, dass die generierte Migration immer sicher ist; beim Umbenennen können Spalten gelöscht werden.
  • Eine Spalte als string typisieren und TypeORM unerwartet varchar(255) inferieren lassen.
  • Geldbeträge als Float speichern und dadurch Präzision verlieren.
  • findOne so behandeln, als würde es einen Fehler werfen; es gibt null zurück, wenn keine Übereinstimmung gefunden wird.
  • Vermischung von Active Record und Data Mapper Patterns innerhalb derselben Entity.

Wie geht es weiter?

TypeORM ist eine komfortable Wahl, wenn Sie mit NestJS arbeiten, und ein grundlegendes Verständnis hilft dabei, Alternativen besser bewerten zu können. Lesen Sie den Prisma-Guide für ein Schema-First ORM mit einem generierten Client oder schauen Sie sich Drizzle ORM an, wenn Sie eine schlanke Schicht bevorzugen, bei der SQL im Vordergrund steht. Um tiefer in die Datenbank selbst einzutauchen, behandelt der PostgreSQL-Guide Indizes, Transaktionen und den Planner, während NestJS zeigt, wie Sie die Services strukturieren, die Ihre Repositories umschließen.

In der Praxis

Entities, Repositories, QueryBuilder, Transaktionen

Die vier Ebenen, die du täglich nutzt – von der Klassendefinition bis zum atomaren Schreibvorgang.

src/entities/post.entity.ts
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  ManyToOne,
  JoinColumn,
  CreateDateColumn,
} from "typeorm";
import { User } from "./user.entity";

@Entity("posts")
export class Post {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  title!: string;

  @Column({ type: "text" })
  body!: string;

  @ManyToOne(() => User, (user) => user.posts, {
    onDelete: "CASCADE",
  })
  @JoinColumn({ name: "author_id" })
  author!: User;

  @Column({ name: "author_id" })
  authorId!: string;

  @CreateDateColumn({ name: "created_at" })
  createdAt!: Date;
}

Repository-Methoden vs. QueryBuilder

Beginne mit Repositories, da sie typisiert sind und schwer falsch zu verwenden sind. Nutze den QueryBuilder, wenn eine Abfrage Joins, Aggregate oder dynamische Filter benötigt.

Repository
const users = await repo.find({
  where: { email: "[email protected]" },
  order: { createdAt: "DESC" },
  take: 10,
});
QueryBuilder
const users = await repo
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.posts", "post")
  .where("user.email = :email", { email })
  .orderBy("user.createdAt", "DESC")
  .take(10)
  .getMany();

Eager Relations vs. explizites Laden

Eager Loading ist bequem, wird aber bei jedem find ausgeführt und kann unbemerkt zu vielen Abfragen führen. Explizite Relationen halten die Kosten an der Aufrufstelle sichtbar.

Bevorzugen
// Explicit: one clear extra join, chosen per query.
const posts = await repo.find({
  relations: { author: true },
  where: { status: "published" },
});
Vermeiden
// Eager on the entity: loads on every single find,
// including the ones that never need the author.
@ManyToOne(() => User, { eager: true })
author!: User;

Abwägungen

Solltest du TypeORM für ein neues Projekt wählen?

TypeORM ist leistungsfähig und NestJS-Teams vertraut, aber das Ökosystem hat sich an einigen Stellen weiterentwickelt. Wäge die Ergonomie gegen die Alternativen ab.

Strengths

  • Vertraut für NestJS-Teams

    @nestjs/typeorm integriert Repositories in den Dependency Injector, sodass Entities und Services wie nativer Nest-Code wirken.

  • Beide ORM-Patterns in einer Library

    Du kannst Data Mapper Repositories für den Großteil des Codes und Active Record für einfache Modelle verwenden, ohne ein zweites Tool einführen zu müssen.

  • Umfangreiche Relationen und Migrationen

    Vier Relationstypen, Cascades, Subscriber und Schema-Diff-Migrationen decken ein breites Spektrum an relationaler Modellierung ab.

Trade-offs

  • Stilles Laden von Relationen

    Lazy- und Eager-Relationen machen es einfach, unbeabsichtigt Abfragen auszulösen, was zu N+1-Problemen in der Produktion führt.

  • Typisierung kann unpräzise sein

    find-where-Klauseln und der QueryBuilder akzeptieren Strings, sodass einige Fehler erst zur Laufzeit und nicht zur Kompilierzeit auftreten.

  • Wartung ist stetig, aber nicht schnell

    Die Entwicklung ist langsamer als bei Prisma oder Drizzle, und offene Issues können länger bestehen bleiben. Prüfe das Repository, bevor du dich langfristig entscheidest.

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, TypeORM zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch TypeORM — mit Quizzen und echtem Code, den Sie im Browser ausführen können.