@10pearls/typeorm 中文文档教程
TypeORM 是一个 ORM 可以在 NodeJS、浏览器、Cordova、PhoneGap、Ionic、React Native、NativeScript、Expo 和 Electron 平台上运行 并且可以与 TypeScript 和 JavaScript(ES5、ES6、ES7、ES8)一起使用。 它的目标是始终支持最新的 JavaScript 特性并提供额外的特性 帮助您开发任何类型的使用数据库的应用程序 - 从 从只有几张表的小型应用程序到大型企业应用程序 与多个数据库。
TypeORM 同时支持 Active Record 和 Data Mapper 模式, 与目前存在的所有其他 JavaScript ORM 不同, 这意味着您可以编写高质量、松散耦合、可扩展、 可维护的应用程序最高效的方式。
TypeORM 深受其他 ORM 的影响,例如 Hibernate, Doctrine 和实体框架。
一些 TypeORM 功能:
- supports both DataMapper and ActiveRecord (your choice)
- entities and columns
- database-specific column types
- entity manager
- repositories and custom repositories
- clean object relational model
- associations (relations)
- eager and lazy relations
- uni-directional, bi-directional and self-referenced relations
- supports multiple inheritance patterns
- cascades
- indices
- transactions
- migrations and automatic migrations generation
- connection pooling
- replication
- using multiple database connections
- working with multiple databases types
- cross-database and cross-schema queries
- elegant-syntax, flexible and powerful QueryBuilder
- left and inner joins
- proper pagination for queries using joins
- query caching
- streaming raw results
- logging
- listeners and subscribers (hooks)
- supports closure table pattern
- schema declaration in models or separate configuration files
- connection configuration in json / xml / yml / env formats
- supports MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js
- supports MongoDB NoSQL database
- works in NodeJS / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron platforms
- TypeScript and JavaScript support
- produced code is performant, flexible, clean and maintainable
- follows all possible best practices
- CLI
以及更多……
使用 TypeORM,您的模型如下所示:
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
您的领域逻辑如下所示:
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await repository.save(user);
const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });
await repository.remove(timber);
或者,如果您更喜欢使用 ActiveRecord
实现,您也可以使用它:
import {Entity, PrimaryGeneratedColumn, Column, BaseEntity} from "typeorm";
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
以及您的域逻辑将如下所示:
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await user.save();
const allUsers = await User.find();
const firstUser = await User.findOne(1);
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });
await timber.remove();
Installation
安装 npm 包:
npm install typeorm --save
您需要安装
reflect-metadata
shim:npm install reflect-metadata - - 保存
并将其导入应用全局位置的某处(例如
app.ts
):import "reflect-metadata";
您可能需要安装节点类型:
npm install @types/node --save
安装数据库驱动程序:
MySQL 或 MariaDB
npm install mysql --save
(你也可以安装mysql2
)for PostgreSQL
npm install pg --save
for SQLite
npm install sqlite3 --save
Microsoft SQL Server
npm install mssql --save for sql.js
npm install sql.js --save
for Oracle
npm install oracledb --save
只安装其中的一个,具体取决于您使用的数据库。
要使 Oracle 驱动程序正常工作,您需要按照安装说明进行操作 他们的站点。
MongoDB(实验性)
npm install mongodb --save
NativeScript、react-native 和 Cordova
检查支持平台的文档
TypeScript configuration
另外,请确保您使用的是 2.3 或更高版本的 TypeScript 编译器, 并且您在 tsconfig.json
中启用了以下设置:
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
您可能还需要在编译器选项的 lib
部分启用 es6
,或者安装@types
中的 es6-shim
。
Quick Start
开始使用 TypeORM 的最快方法是使用其 CLI 命令生成一个入门项目。 只有在 NodeJS 应用程序中使用 TypeORM 时,快速入门才有效。 如果您使用的是其他平台,请继续阅读分步指南。
首先,全局安装TypeORM:
npm install typeorm -g
然后进入你要创建新项目的目录,运行命令:
typeorm init --name MyProject --database mysql
其中name
是你的项目名称,database
是您将使用的数据库。 数据库可以是以下值之一:mysql
、mariadb
、postgres
、sqlite
、mssql< /code>,
oracle
, mongodb
, cordova
、react-native
、expo
、nativescript
。
此命令将在 MyProject
目录中生成一个包含以下文件的新项目:
MyProject
├── src // place of your TypeScript code
│ ├── entity // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migration // place where your migrations are stored
│ └── index.ts // start point of your application
├── .gitignore // standard gitignore file
├── ormconfig.json // ORM and database connection configuration
├── package.json // node module dependencies
├── README.md // simple readme file
└── tsconfig.json // TypeScript compiler options
您也可以在现有节点项目上运行
typeorm init
,但要小心 - 它可能会覆盖一些您已有的文件。
下一步是安装新的项目依赖项:
cd MyProject
npm install
在安装过程中,编辑 ormconfig.json
文件并在其中放置您自己的数据库连接配置选项:
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "test",
"password": "test",
"database": "test",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
]
}
特别是,大多数时候您只会需要配置 host
、username
、password
、database
和 port
选项。
完成配置并安装所有节点模块后,您就可以运行您的应用程序了:
npm start
就是这样,您的应用程序应该成功运行并将一个新用户插入到数据库中。 您可以继续使用此项目并集成您需要的其他模块并开始 创建更多实体。
您可以通过运行安装 express 来生成更高级的项目
typeorm init --name MyProject --database mysql --express
命令。
Step-by-Step Guide
您对 ORM 有什么期望? 首先,您期望它会为您创建数据库表 轻松查找/插入/更新/删除您的数据 不得不编写大量难以维护的 SQL 查询。 本指南将向您展示如何从头开始设置 TypeORM 并使其执行您对 ORM 的期望。
Create a model
使用数据库从创建表开始。 你如何告诉 TypeORM 创建数据库表? 答案是——通过模型。 您应用中的模型就是您的数据库表。
例如,您有一个 Photo
模型:
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
}
您希望将照片存储在数据库中。 要在数据库中存储东西,首先你需要一个数据库表, 和数据库表是根据您的模型创建的。 不是所有模型,而是您定义为实体的模型。
Create an entity
Entity 是由 @Entity
装饰器装饰的模型。 将为此类模型创建一个数据库表。 您可以使用 TypeORM 在任何地方处理实体。 您可以使用它们加载/插入/更新/删除和执行其他操作。
让我们将我们的 Photo
模型作为一个实体:
import {Entity} from "typeorm";
@Entity()
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
isPublished: boolean;
}
现在,将为 Photo
实体创建一个数据库表,我们将能够在我们的应用程序的任何地方使用它。 我们创建了一个数据库表,但是没有列的表可以存在吗? 让我们在数据库表中创建一些列。
Adding table columns
要添加数据库列,您只需将要制作的实体的属性装饰成列 使用 @Column
装饰器。
import {Entity, Column} from "typeorm";
@Entity()
export class Photo {
@Column()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
现在 id
、name
、description
、filename
、views
和 isPublished
列将添加到 photo
表中。 数据库中的列类型是从您使用的属性类型推断出来的,例如 number
会被转成integer
,string
会转成varchar
,boolean
会转成bool
等 但是您可以通过在 @Column
装饰器中隐式指定一个列类型来使用您的数据库支持的任何列类型。
我们生成了一个包含列的数据库表,但还剩下一件事。 每个数据库表都必须有一个带有主键的列。
Creating a primary column
每个实体必须至少有一个主键列。 这是一项要求,您无法避免。 要使列成为主键,您需要使用 @PrimaryColumn
装饰器。
import {Entity, Column, PrimaryColumn} from "typeorm";
@Entity()
export class Photo {
@PrimaryColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
Creating an auto generated column
现在,假设您希望自动生成您的 id 列(这称为自动递增/序列/序列/生成的标识列)。 为此,您需要将 @PrimaryColumn
装饰器更改为 @PrimaryGeneratedColumn
装饰器:
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
Column data types
接下来,让我们修复我们的数据类型。 默认情况下,字符串被映射到类似 varchar(255) 的类型(取决于数据库类型)。 数字被映射到类似整数的类型(取决于数据库类型)。 我们不希望我们所有的列都是有限的 varchars 或整数。 让我们设置正确的数据类型:
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 100
})
name: string;
@Column("text")
description: string;
@Column()
filename: string;
@Column("double")
views: number;
@Column()
isPublished: boolean;
}
列类型是特定于数据库的。 您可以设置数据库支持的任何列类型。 有关支持的列类型的更多信息,请参见此处。
Creating a connection to the database
现在,当我们的实体被创建时,让我们创建一个 index.ts
(或 app.ts
无论你怎么称呼它)文件并在那里设置我们的连接:
import "reflect-metadata";
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
Photo
],
synchronize: true,
logging: false
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));
我们正在使用 MySQL在此示例中,但您可以使用任何其他受支持的数据库。 要使用另一个数据库,只需将选项中的 type
更改为您正在使用的数据库类型: mysql、mariadb、postgres、sqlite、mssql、oracle、cordova、nativescript、react-native、expo 或 mongodb。 还要确保使用您自己的主机、端口、用户名、密码和数据库设置。
我们将 Photo 实体添加到此连接的实体列表中。 您在连接中使用的每个实体都必须列在那里。
设置 synchronize
可确保您的实体在每次运行应用程序时都与数据库同步。
Loading all entities from the directory
稍后,当我们创建更多实体时,我们需要将它们添加到配置中的实体中。 这不是很方便,所以我们可以设置整个目录,所有实体都将从那里连接并在我们的连接中使用:
import {createConnection} from "typeorm";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
__dirname + "/entity/*.js"
],
synchronize: true,
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));
但要小心使用这种方法。 如果您使用的是 ts-node
,那么您需要指定 .ts
文件的路径。 如果您使用 outDir
,那么您需要在 outDir 目录中指定 .js
文件的路径。 如果您正在使用 outDir
并且当您删除或重命名您的实体时,请确保清除 outDir
目录 并再次重新编译您的项目,因为当您删除源 .ts
文件时,它们的编译 .js
版本 不会从输出目录中删除,并且仍然由 TypeORM 加载,因为它们存在于 outDir 目录中。
Running the application
现在,如果您运行 index.ts
,将初始化与数据库的连接,并为您的照片创建一个数据库表。
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(500) | |
| description | text | |
| filename | varchar(255) | |
| views | int(11) | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+
Creating and inserting a photo into the database
现在让我们创建一张新照片以将其保存在数据库中:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(connection => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
return connection.manager
.save(photo)
.then(photo => {
console.log("Photo has been saved. Photo id is", photo.id);
});
}).catch(error => console.log(error));
一旦您的实体被保存,它将获得一个新生成的 ID。 save
方法返回您传递给它的同一对象的实例。 它不是对象的新副本,它修改了它的“id”并返回它。
Using async/await syntax
让我们利用最新的 ES8 (ES2017) 功能并改用 async/await 语法:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
await connection.manager.save(photo);
console.log("Photo has been saved");
}).catch(error => console.log(error));
Using Entity Manager
我们刚刚创建了一张新照片并将其保存在数据库中。 我们使用 EntityManager
来保存它。 使用实体管理器,您可以操作应用程序中的任何实体。 例如,让我们加载已保存的实体:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let savedPhotos = await connection.manager.find(Photo);
console.log("All photos from the db: ", savedPhotos);
}).catch(error => console.log(error));
savedPhotos
将是一个包含从数据库加载的数据的 Photo 对象数组。
在此处了解有关 EntityManager 的更多信息。
Using Repositories
现在让我们重构我们的代码并使用 Repository
而不是 EntityManager
。 每个实体都有自己的存储库,用于处理与其实体相关的所有操作。 当您经常与实体打交道时,使用 Repositories 比 EntityManagers 更方便:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
let photoRepository = connection.getRepository(Photo);
await photoRepository.save(photo);
console.log("Photo has been saved");
let savedPhotos = await photoRepository.find();
console.log("All photos from the db: ", savedPhotos);
}).catch(error => console.log(error));
此处了解有关 Repository 的更多信息。
Loading from the database
让我们尝试使用存储库进行更多加载操作:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let allPhotos = await photoRepository.find();
console.log("All photos from the db: ", allPhotos);
let firstPhoto = await photoRepository.findOne(1);
console.log("First photo from the db: ", firstPhoto);
let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" });
console.log("Me and Bears photo from the db: ", meAndBearsPhoto);
let allViewedPhotos = await photoRepository.find({ views: 1 });
console.log("All viewed photos: ", allViewedPhotos);
let allPublishedPhotos = await photoRepository.find({ isPublished: true });
console.log("All published photos: ", allPublishedPhotos);
let [allPhotos, photosCount] = await photoRepository.findAndCount();
console.log("All photos: ", allPhotos);
console.log("Photos count: ", photosCount);
}).catch(error => console.log(error));
Updating in the database
现在让我们从数据库加载一张照片,更新它并保存它:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let photoToUpdate = await photoRepository.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await photoRepository.save(photoToUpdate);
}).catch(error => console.log(error));
现在 id = 1
的照片将在数据库中更新。
Removing from the database
现在让我们从数据库中删除我们的照片:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let photoToRemove = await photoRepository.findOne(1);
await photoRepository.remove(photoToRemove);
}).catch(error => console.log(error));
现在带有 id = 1
的照片将从数据库中删除。
Creating a one-to-one relation
让我们创建与另一个类的一对一关系。 让我们在 PhotoMetadata.ts
中创建一个新类。 这个 PhotoMetadata 类应该包含我们照片的附加元信息:
import {Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class PhotoMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column("int")
height: number;
@Column("int")
width: number;
@Column()
orientation: string;
@Column()
compressed: boolean;
@Column()
comment: string;
@OneToOne(type => Photo)
@JoinColumn()
photo: Photo;
}
在这里,我们使用一个名为 @OneToOne
的新装饰器。 它允许我们在两个实体之间创建一对一的关系。 <代码>类型 => Photo 是一个函数,它返回我们想要与之建立关系的实体的类。 由于语言的特殊性,我们被迫使用返回类的函数,而不是直接使用类。 我们也可以把它写成 () => 照片
,但我们使用type => 照片
作为提高代码可读性的约定。 类型变量本身不包含任何内容。
我们还添加了一个 @JoinColumn
装饰器,它表示关系的这一端将拥有该关系。 关系可以是单向的或双向的。 只有关系的一面可以是拥有。 在关系的所有者端需要使用 @JoinColumn
装饰器。
如果您运行该应用程序,您将看到一个新生成的表,它将包含一个带有照片关系外键的列:
+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| height | int(11) | |
| width | int(11) | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Save a one-to-one relation
现在让我们保存一张照片及其元数据并将它们相互关联。
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";
createConnection(/*...*/).then(async connection => {
// create a photo
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.isPublished = true;
// create a photo metadata
let metadata = new PhotoMetadata();
metadata.height = 640;
metadata.width = 480;
metadata.compressed = true;
metadata.comment = "cybershoot";
metadata.orientation = "portait";
metadata.photo = photo; // this way we connect them
// get entity repositories
let photoRepository = connection.getRepository(Photo);
let metadataRepository = connection.getRepository(PhotoMetadata);
// first we should save a photo
await photoRepository.save(photo);
// photo is saved. Now we need to save a photo metadata
await metadataRepository.save(metadata);
// done
console.log("Metadata is saved, and relation between metadata and photo is created in the database too");
}).catch(error => console.log(error));
Inverse side of the relationship
关系可以是单向的或双向的。 目前,我们的 PhotoMetadata 和 Photo 之间的关系是单向的。 关系的所有者是 PhotoMetadata,而 Photo 对 PhotoMetadata 一无所知。 这使得从照片端访问照片元数据变得复杂。 要解决这个问题,我们应该添加一个反向关系,并使 PhotoMetadata 和 Photo 之间的关系成为双向的。 让我们修改我们的实体:
import {Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(type => Photo, photo => photo.metadata)
@JoinColumn()
photo: Photo;
}
import {Entity, Column, PrimaryGeneratedColumn, OneToOne} from "typeorm";
import {PhotoMetadata} from "./PhotoMetadata";
@Entity()
export class Photo {
/* ... other columns */
@OneToOne(type => PhotoMetadata, photoMetadata => photoMetadata.photo)
metadata: PhotoMetadata;
}
photo => photo.metadata
是一个返回关系反面名称的函数。 这里我们展示了 Photo 类的元数据属性是我们在 Photo 类中存储 PhotoMetadata 的地方。 除了传递返回照片属性的函数之外,您还可以简单地将字符串传递给 @OneToOne
装饰器,例如 "metadata"
。 但是我们使用这种函数类型的方法来使我们的重构更容易。
请注意,我们应该只在关系的一侧使用 @JoinColumn
装饰器。 无论你把这个装饰器放在哪一边,都将成为关系的拥有方。 关系的拥有方包含数据库中带有外键的列。
Loading objects with their relations
现在让我们在单个查询中加载我们的照片及其照片元数据。 有两种方法 - 使用 find*
方法或使用 QueryBuilder
功能。 让我们先使用 find*
方法。 find*
方法允许您使用 FindOneOptions
/ FindManyOptions
接口指定一个对象。
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";
createConnection(/*...*/).then(async connection => {
/*...*/
let photoRepository = connection.getRepository(Photo);
let photos = await photoRepository.find({ relations: ["metadata"] });
}).catch(error => console.log(error));
在这里,照片将包含数据库中的一组照片,每张照片都将包含其照片元数据。 在本文档中了解有关查找选项的更多信息。
使用查找选项既好又简单,但如果您需要更复杂的查询,则应改用 QueryBuilder
。 QueryBuilder
允许以优雅的方式使用更复杂的查询:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";
createConnection(/*...*/).then(async connection => {
/*...*/
let photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo")
.innerJoinAndSelect("photo.metadata", "metadata")
.getMany();
}).catch(error => console.log(error));
QueryBuilder
允许创建和执行几乎任何复杂性的 SQL 查询。 当您使用 QueryBuilder
时,请像您正在创建 SQL 查询一样思考。 在此示例中,“照片”和“元数据”是应用于所选照片的别名。 您使用别名来访问所选数据的列和属性。
Using cascades to automatically save related objects
我们可以在我们的关系中设置级联选项,在我们希望在保存其他对象时保存我们的相关对象的情况下。 让我们稍微更改一下照片的 @OneToOne
装饰器:
export class Photo {
/// ... other columns
@OneToOne(type => PhotoMetadata, metadata => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata;
}
使用 cascade
允许我们现在不单独保存照片和单独保存元数据对象。 现在我们可以简单地保存一个照片对象,由于级联选项,元数据对象将自动保存。
createConnection(options).then(async connection => {
// create photo object
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.isPublished = true;
// create photo metadata object
let metadata = new PhotoMetadata();
metadata.height = 640;
metadata.width = 480;
metadata.compressed = true;
metadata.comment = "cybershoot";
metadata.orientation = "portait";
photo.metadata = metadata; // this way we connect them
// get repository
let photoRepository = connection.getRepository(Photo);
// saving a photo also save the metadata
await photoRepository.save(photo);
console.log("Photo is saved, photo metadata is saved too.")
}).catch(error => console.log(error));
Creating a many-to-one / one-to-many relation
让我们创建一个多对一/一对多关系。 假设一张照片有一个作者,每个作者可以有很多张照片。 首先,让我们创建一个 Author
类:
import {Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(type => Photo, photo => photo.author) // note: we will create author property in the Photo class below
photos: Photo[];
}
Author
包含关系的反面。 OneToMany
始终是关系的反面,如果没有 ManyToOne
在关系的另一边,它就不可能存在。
现在让我们将关系的所有者端添加到照片实体中:
import {Entity, Column, PrimaryGeneratedColumn, ManyToOne} from "typeorm";
import {PhotoMetadata} from "./PhotoMetadata";
import {Author} from "./Author";
@Entity()
export class Photo {
/* ... other columns */
@ManyToOne(type => Author, author => author.photos)
author: Author;
}
在多对一/一对多关系中,所有者端始终是多对一的。 这意味着使用 @ManyToOne
的类将存储相关对象的 ID。
运行应用程序后,ORM 将创建 author
表:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
它还将修改 photo
表,添加一个新的 author
列并创建它的外键:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Creating a many-to-many relation
让我们创建一个多对一/多对多关系。 假设一张照片可以在多个相册中,每个相册可以包含很多照片。 让我们创建一个 Album
类:
import {Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable} from "typeorm";
@Entity()
export class Album {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(type => Photo, photo => photo.albums)
@JoinTable()
photos: Photo[];
}
@JoinTable
需要指定这是关系的所有者方。
现在让我们添加与 Photo
类的关系的反面:
export class Photo {
/// ... other columns
@ManyToMany(type => Album, album => album.photos)
albums: Album[];
}
运行应用程序后,ORM 将创建一个albumphotosphoto_albums < em>联结表:
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
不要忘记在 ORM 中使用您的连接注册 Album
类:
const options: ConnectionOptions = {
// ... other options
entities: [Photo, PhotoMetadata, Author, Album]
};
现在让我们将相册和照片插入我们的数据库:
let connection = await createConnection(options);
// create a few albums
let album1 = new Album();
album1.name = "Bears";
await connection.manager.save(album1);
let album2 = new Album();
album2.name = "Me";
await connection.manager.save(album2);
// create a few photos
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.albums = [album1, album2];
await connection.manager.save(photo);
// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await connection
.getRepository(Photo)
.findOne(1, { relations: ["albums"] });
loadedPhoto
将等于:
{
id: 1,
name: "Me and Bears",
description: "I am near polar bears",
filename: "photo-with-bears.jpg",
albums: [{
id: 1,
name: "Bears"
}, {
id: 2,
name: "Me"
}]
}
Using QueryBuilder
您可以使用 QueryBuilder 来构建几乎任何复杂性的 SQL 查询。 例如,您可以这样做:
let photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
.innerJoinAndSelect("photo.metadata", "metadata")
.leftJoinAndSelect("photo.albums", "album")
.where("photo.isPublished = true")
.andWhere("(photo.name = :photoName OR photo.name = :bearName)")
.orderBy("photo.id", "DESC")
.skip(5)
.take(10)
.setParameters({ photoName: "My", bearName: "Mishka" })
.getMany();
此查询选择名称为“My”或“Mishka”的所有已发布照片。 它将从位置 5(分页偏移量)选择结果, 并且只会选择 10 个结果(分页限制)。 选择结果将按照id降序排列。 照片的相册将左连接,其元数据将被内连接。
您将在应用程序中经常使用查询生成器。 在此处了解有关 QueryBuilder 的更多信息。
Samples
查看sample 中的示例以获取使用示例。
您可以克隆并开始使用一些存储库:
- Example how to use TypeORM with TypeScript
- Example how to use TypeORM with JavaScript
- Example how to use TypeORM with JavaScript and Babel
- Example how to use TypeORM with TypeScript and SystemJS in Browser
- Example how to use Express and TypeORM
- Example how to use Koa and TypeORM
- Example how to use TypeORM with MongoDB
- Example how to use TypeORM in a Cordova/PhoneGap app
- Example how to use TypeORM with an Ionic app
- Example how to use TypeORM with React Native
- Example how to use TypeORM with Electron using JavaScript
- Example how to use TypeORM with Electron using TypeScript
Extensions
有几个扩展可以简化 TypeORM 的使用并将其与其他模块集成:
- TypeORM + GraphQL framework
- TypeORM integration with TypeDI
- TypeORM integration with routing-controllers
- Models generation from existing database - typeorm-model-generator
Contributing
这个项目的存在要感谢所有做出贡献的人:
Sponsors
开源既辛苦又费时。 如果你想投资 TypeORM 的未来,你可以成为赞助商,让我们的核心团队花更多时间在 TypeORM 的改进和新功能上。 成为赞助商
Gold Sponsors
成为金牌赞助商并从我们的核心贡献者那里获得高级技术支持。 成为金牌赞助商
TypeORM is an ORM that can run in NodeJS, Browser, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo, and Electron platforms and can be used with TypeScript and JavaScript (ES5, ES6, ES7, ES8). Its goal is to always support the latest JavaScript features and provide additional features that help you to develop any kind of application that uses databases - from small applications with a few tables to large scale enterprise applications with multiple databases.
TypeORM supports both Active Record and Data Mapper patterns, unlike all other JavaScript ORMs currently in existance, which means you can write high quality, loosely coupled, scalable, maintainable applications the most productive way.
TypeORM is highly influenced by other ORMs, such as Hibernate, Doctrine and Entity Framework.
Some TypeORM features:
- supports both DataMapper and ActiveRecord (your choice)
- entities and columns
- database-specific column types
- entity manager
- repositories and custom repositories
- clean object relational model
- associations (relations)
- eager and lazy relations
- uni-directional, bi-directional and self-referenced relations
- supports multiple inheritance patterns
- cascades
- indices
- transactions
- migrations and automatic migrations generation
- connection pooling
- replication
- using multiple database connections
- working with multiple databases types
- cross-database and cross-schema queries
- elegant-syntax, flexible and powerful QueryBuilder
- left and inner joins
- proper pagination for queries using joins
- query caching
- streaming raw results
- logging
- listeners and subscribers (hooks)
- supports closure table pattern
- schema declaration in models or separate configuration files
- connection configuration in json / xml / yml / env formats
- supports MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js
- supports MongoDB NoSQL database
- works in NodeJS / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron platforms
- TypeScript and JavaScript support
- produced code is performant, flexible, clean and maintainable
- follows all possible best practices
- CLI
And more…
With TypeORM your models look like this:
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
And your domain logic looks like this:
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await repository.save(user);
const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });
await repository.remove(timber);
Alternatively, if you prefer to use the ActiveRecord
implementation, you can use it as well:
import {Entity, PrimaryGeneratedColumn, Column, BaseEntity} from "typeorm";
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
And your domain logic will look this way:
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await user.save();
const allUsers = await User.find();
const firstUser = await User.findOne(1);
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });
await timber.remove();
Installation
Install the npm package:
npm install typeorm --save
You need to install
reflect-metadata
shim:npm install reflect-metadata --save
and import it somewhere in the global place of your app (for example in
app.ts
):import "reflect-metadata";
You may need to install node typings:
npm install @types/node --save
Install a database driver:
for MySQL or MariaDB
npm install mysql --save
(you can installmysql2
instead as well)for PostgreSQL
npm install pg --save
for SQLite
npm install sqlite3 --save
for Microsoft SQL Server
npm install mssql --save
for sql.js
npm install sql.js --save
for Oracle
npm install oracledb --save
Install only one of them, depending on which database you use.
To make the Oracle driver work, you need to follow the installation instructions from their site.
for MongoDB (experimental)
npm install mongodb --save
for NativeScript, react-native and Cordova
TypeScript configuration
Also, make sure you are using TypeScript compiler version 2.3 or greater, and you have enabled the following settings in tsconfig.json
:
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
You may also need to enable es6
in the lib
section of compiler options, or install es6-shim
from @types
.
Quick Start
The quickest way to get started with TypeORM is to use its CLI commands to generate a starter project. Quick start works only if you are using TypeORM in a NodeJS application. If you are using other platforms, proceed to the step-by-step guide.
First, install TypeORM globally:
npm install typeorm -g
Then go to the directory where you want to create a new project and run the command:
typeorm init --name MyProject --database mysql
Where name
is the name of your project and database
is the database you'll use. Database can be one of the following values: mysql
, mariadb
, postgres
, sqlite
, mssql
, oracle
, mongodb
, cordova
, react-native
, expo
, nativescript
.
This command will generate a new project in the MyProject
directory with the following files:
MyProject
├── src // place of your TypeScript code
│ ├── entity // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migration // place where your migrations are stored
│ └── index.ts // start point of your application
├── .gitignore // standard gitignore file
├── ormconfig.json // ORM and database connection configuration
├── package.json // node module dependencies
├── README.md // simple readme file
└── tsconfig.json // TypeScript compiler options
You can also run
typeorm init
on an existing node project, but be careful - it may override some files you already have.
The next step is to install new project dependencies:
cd MyProject
npm install
While installation is in progress, edit the ormconfig.json
file and put your own database connection configuration options in there:
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "test",
"password": "test",
"database": "test",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
]
}
Particularly, most of the time you'll only need to configure host
, username
, password
, database
and maybe port
options.
Once you finish with configuration and all node modules are installed, you can run your application:
npm start
That's it, your application should successfully run and insert a new user into the database. You can continue to work with this project and integrate other modules you need and start creating more entities.
You can generate an even more advanced project with express installed by running
typeorm init --name MyProject --database mysql --express
command.
Step-by-Step Guide
What are you expecting from ORM? First of all, you are expecting it will create database tables for you and find / insert / update / delete your data without the pain of having to write lots of hardly maintainable SQL queries. This guide will show you how to setup TypeORM from scratch and make it do what you are expecting from an ORM.
Create a model
Working with a database starts from creating tables. How do you tell TypeORM to create a database table? The answer is - through the models. Your models in your app are your database tables.
For example, you have a Photo
model:
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
}
And you want to store photos in your database. To store things in the database, first you need a database table, and database tables are created from your models. Not all models, but only those you define as entities.
Create an entity
Entity is your model decorated by an @Entity
decorator. A database table will be created for such models. You work with entities everywhere with TypeORM. You can load/insert/update/remove and perform other operations with them.
Let's make our Photo
model as an entity:
import {Entity} from "typeorm";
@Entity()
export class Photo {
id: number;
name: string;
description: string;
filename: string;
views: number;
isPublished: boolean;
}
Now, a database table will be created for the Photo
entity and we'll be able to work with it anywhere in our app. We have created a database table, however what table can exist without columns? Let's create a few columns in our database table.
Adding table columns
To add database columns, you simply need to decorate an entity's properties you want to make into a column with a @Column
decorator.
import {Entity, Column} from "typeorm";
@Entity()
export class Photo {
@Column()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
Now id
, name
, description
, filename
, views
and isPublished
columns will be added to the photo
table. Column types in the database are inferred from the property types you used, e.g. number
will be converted into integer
, string
into varchar
, boolean
into bool
, etc. But you can use any column type your database supports by implicitly specifying a column type into the @Column
decorator.
We generated a database table with columns, but there is one thing left. Each database table must have a column with a primary key.
Creating a primary column
Each entity must have at least one primary key column. This is a requirement and you can't avoid it. To make a column a primary key, you need to use @PrimaryColumn
decorator.
import {Entity, Column, PrimaryColumn} from "typeorm";
@Entity()
export class Photo {
@PrimaryColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
Creating an auto generated column
Now, let's say you want your id column to be auto-generated (this is known as auto-increment / sequence / serial / generated identity column). To do that, you need to change the @PrimaryColumn
decorator to a @PrimaryGeneratedColumn
decorator:
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
Column data types
Next, let's fix our data types. By default, string is mapped to a varchar(255)-like type (depending on the database type). Number is mapped to a integer-like type (depending on the database type). We don't want all our columns to be limited varchars or integers. Let's setup correct data types:
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 100
})
name: string;
@Column("text")
description: string;
@Column()
filename: string;
@Column("double")
views: number;
@Column()
isPublished: boolean;
}
Column types are database-specific. You can set any column type your database supports. More information on supported column types can be found here.
Creating a connection to the database
Now, when our entity is created, let's create an index.ts
(or app.ts
whatever you call it) file and set up our connection there:
import "reflect-metadata";
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
Photo
],
synchronize: true,
logging: false
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));
We are using MySQL in this example, but you can use any other supported database. To use another database, simply change the type
in the options to the database type you are using: mysql, mariadb, postgres, sqlite, mssql, oracle, cordova, nativescript, react-native, expo, or mongodb. Also make sure to use your own host, port, username, password and database settings.
We added our Photo entity to the list of entities for this connection. Each entity you are using in your connection must be listed there.
Setting synchronize
makes sure your entities will be synced with the database, every time you run the application.
Loading all entities from the directory
Later, when we create more entities we need to add them to the entities in our configuration. This is not very convenient, so instead we can set up the whole directory, from where all entities will be connected and used in our connection:
import {createConnection} from "typeorm";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
__dirname + "/entity/*.js"
],
synchronize: true,
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));
But be careful with this approach. If you are using ts-node
then you need to specify paths to .ts
files instead. If you are using outDir
then you'll need to specify paths to .js
files inside outDir directory. If you are using outDir
and when you remove or rename your entities make sure to clear outDir
directory and re-compile your project again, because when you remove your source .ts
files their compiled .js
versions aren't removed from output directory and still are loaded by TypeORM because they present in outDir directory.
Running the application
Now if you run your index.ts
, a connection with database will be initialized and a database table for your photos will be created.
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(500) | |
| description | text | |
| filename | varchar(255) | |
| views | int(11) | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+
Creating and inserting a photo into the database
Now let's create a new photo to save it in the database:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(connection => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
return connection.manager
.save(photo)
.then(photo => {
console.log("Photo has been saved. Photo id is", photo.id);
});
}).catch(error => console.log(error));
Once your entity is saved it will get a newly generated id. save
method returns an instance of the same object you pass to it. It's not a new copy of the object, it modifies its "id" and returns it.
Using async/await syntax
Let's take advantage of the latest ES8 (ES2017) features and use async/await syntax instead:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
await connection.manager.save(photo);
console.log("Photo has been saved");
}).catch(error => console.log(error));
Using Entity Manager
We just created a new photo and saved it in the database. We used EntityManager
to save it. Using entity manager you can manipulate any entity in your app. For example, let's load our saved entity:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let savedPhotos = await connection.manager.find(Photo);
console.log("All photos from the db: ", savedPhotos);
}).catch(error => console.log(error));
savedPhotos
will be an array of Photo objects with the data loaded from the database.
Learn more about EntityManager here.
Using Repositories
Now let's refactor our code and use Repository
instead of EntityManager
. Each entity has its own repository which handles all operations with its entity. When you deal with entities a lot, Repositories are more convenient to use than EntityManagers:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
let photoRepository = connection.getRepository(Photo);
await photoRepository.save(photo);
console.log("Photo has been saved");
let savedPhotos = await photoRepository.find();
console.log("All photos from the db: ", savedPhotos);
}).catch(error => console.log(error));
Learn more about Repository here.
Loading from the database
Let's try more load operations using the Repository:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let allPhotos = await photoRepository.find();
console.log("All photos from the db: ", allPhotos);
let firstPhoto = await photoRepository.findOne(1);
console.log("First photo from the db: ", firstPhoto);
let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" });
console.log("Me and Bears photo from the db: ", meAndBearsPhoto);
let allViewedPhotos = await photoRepository.find({ views: 1 });
console.log("All viewed photos: ", allViewedPhotos);
let allPublishedPhotos = await photoRepository.find({ isPublished: true });
console.log("All published photos: ", allPublishedPhotos);
let [allPhotos, photosCount] = await photoRepository.findAndCount();
console.log("All photos: ", allPhotos);
console.log("Photos count: ", photosCount);
}).catch(error => console.log(error));
Updating in the database
Now let's load a single photo from the database, update it and save it:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let photoToUpdate = await photoRepository.findOne(1);
photoToUpdate.name = "Me, my friends and polar bears";
await photoRepository.save(photoToUpdate);
}).catch(error => console.log(error));
Now photo with id = 1
will be updated in the database.
Removing from the database
Now let's remove our photo from the database:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
createConnection(/*...*/).then(async connection => {
/*...*/
let photoToRemove = await photoRepository.findOne(1);
await photoRepository.remove(photoToRemove);
}).catch(error => console.log(error));
Now photo with id = 1
will be removed from the database.
Creating a one-to-one relation
Let's create a one-to-one relation with another class. Let's create a new class in PhotoMetadata.ts
. This PhotoMetadata class is supposed to contain our photo's additional meta-information:
import {Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class PhotoMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column("int")
height: number;
@Column("int")
width: number;
@Column()
orientation: string;
@Column()
compressed: boolean;
@Column()
comment: string;
@OneToOne(type => Photo)
@JoinColumn()
photo: Photo;
}
Here, we are using a new decorator called @OneToOne
. It allows us to create a one-to-one relationship between two entities. type => Photo
is a function that returns the class of the entity with which we want to make our relationship. We are forced to use a function that returns a class, instead of using the class directly, because of the language specifics. We can also write it as () => Photo
, but we use type => Photo
as a convention to increase code readability. The type variable itself does not contain anything.
We also add a @JoinColumn
decorator, which indicates that this side of the relationship will own the relationship. Relations can be unidirectional or bidirectional. Only one side of relational can be owning. Using @JoinColumn
decorator is required on the owner side of the relationship.
If you run the app, you'll see a newly generated table, and it will contain a column with a foreign key for the photo relation:
+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| height | int(11) | |
| width | int(11) | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Save a one-to-one relation
Now let's save a photo, its metadata and attach them to each other.
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";
createConnection(/*...*/).then(async connection => {
// create a photo
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.isPublished = true;
// create a photo metadata
let metadata = new PhotoMetadata();
metadata.height = 640;
metadata.width = 480;
metadata.compressed = true;
metadata.comment = "cybershoot";
metadata.orientation = "portait";
metadata.photo = photo; // this way we connect them
// get entity repositories
let photoRepository = connection.getRepository(Photo);
let metadataRepository = connection.getRepository(PhotoMetadata);
// first we should save a photo
await photoRepository.save(photo);
// photo is saved. Now we need to save a photo metadata
await metadataRepository.save(metadata);
// done
console.log("Metadata is saved, and relation between metadata and photo is created in the database too");
}).catch(error => console.log(error));
Inverse side of the relationship
Relations can be unidirectional or bidirectional. Currently, our relation between PhotoMetadata and Photo is unidirectional. The owner of the relation is PhotoMetadata, and Photo doesn't know anything about PhotoMetadata. This makes it complicated to access PhotoMetadata from the Photo side. To fix this issue we should add an inverse relation, and make relations between PhotoMetadata and Photo bidirectional. Let's modify our entities:
import {Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(type => Photo, photo => photo.metadata)
@JoinColumn()
photo: Photo;
}
import {Entity, Column, PrimaryGeneratedColumn, OneToOne} from "typeorm";
import {PhotoMetadata} from "./PhotoMetadata";
@Entity()
export class Photo {
/* ... other columns */
@OneToOne(type => PhotoMetadata, photoMetadata => photoMetadata.photo)
metadata: PhotoMetadata;
}
photo => photo.metadata
is a function that returns the name of the inverse side of the relation. Here we show that the metadata property of the Photo class is where we store PhotoMetadata in the Photo class. Instead of passing a function that returns a property of the photo, you could alternatively simply pass a string to @OneToOne
decorator, like "metadata"
. But we used this function-typed approach to make our refactoring easier.
Note that we should use @JoinColumn
decorator only on one side of a relation. Whichever side you put this decorator on will be the owning side of the relationship. The owning side of a relationship contains a column with a foreign key in the database.
Loading objects with their relations
Now let's load our photo and its photo metadata in a single query. There are two ways to do it - using find*
methods or using QueryBuilder
functionality. Let's use find*
methods first. find*
methods allow you to specify an object with the FindOneOptions
/ FindManyOptions
interface.
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";
createConnection(/*...*/).then(async connection => {
/*...*/
let photoRepository = connection.getRepository(Photo);
let photos = await photoRepository.find({ relations: ["metadata"] });
}).catch(error => console.log(error));
Here, photos will contain an array of photos from the database, and each photo will contain its photo metadata. Learn more about Find Options in this documentation.
Using find options is good and dead simple, but if you need a more complex query, you should use QueryBuilder
instead. QueryBuilder
allows more complex queries to be used in an elegant way:
import {createConnection} from "typeorm";
import {Photo} from "./entity/Photo";
import {PhotoMetadata} from "./entity/PhotoMetadata";
createConnection(/*...*/).then(async connection => {
/*...*/
let photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo")
.innerJoinAndSelect("photo.metadata", "metadata")
.getMany();
}).catch(error => console.log(error));
QueryBuilder
allows creation and execution of SQL queries of almost any complexity. When you work with QueryBuilder
, think like you are creating an SQL query. In this example, "photo" and "metadata" are aliases applied to selected photos. You use aliases to access columns and properties of the selected data.
Using cascades to automatically save related objects
We can setup cascade options in our relations, in the cases when we want our related object to be saved whenever the other object is saved. Let's change our photo's @OneToOne
decorator a bit:
export class Photo {
/// ... other columns
@OneToOne(type => PhotoMetadata, metadata => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata;
}
Using cascade
allows us not to separately save photo and separately save metadata objects now. Now we can simply save a photo object, and the metadata object will be saved automatically because of cascade options.
createConnection(options).then(async connection => {
// create photo object
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.isPublished = true;
// create photo metadata object
let metadata = new PhotoMetadata();
metadata.height = 640;
metadata.width = 480;
metadata.compressed = true;
metadata.comment = "cybershoot";
metadata.orientation = "portait";
photo.metadata = metadata; // this way we connect them
// get repository
let photoRepository = connection.getRepository(Photo);
// saving a photo also save the metadata
await photoRepository.save(photo);
console.log("Photo is saved, photo metadata is saved too.")
}).catch(error => console.log(error));
Creating a many-to-one / one-to-many relation
Let's create a many-to-one / one-to-many relation. Let's say a photo has one author, and each author can have many photos. First, let's create an Author
class:
import {Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(type => Photo, photo => photo.author) // note: we will create author property in the Photo class below
photos: Photo[];
}
Author
contains an inverse side of a relation. OneToMany
is always an inverse side of relation, and it can't exist without ManyToOne
on the other side of the relation.
Now let's add the owner side of the relation into the Photo entity:
import {Entity, Column, PrimaryGeneratedColumn, ManyToOne} from "typeorm";
import {PhotoMetadata} from "./PhotoMetadata";
import {Author} from "./Author";
@Entity()
export class Photo {
/* ... other columns */
@ManyToOne(type => Author, author => author.photos)
author: Author;
}
In many-to-one / one-to-many relation, the owner side is always many-to-one. It means that the class that uses @ManyToOne
will store the id of the related object.
After you run the application, the ORM will create the author
table:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
It will also modify the photo
table, adding a new author
column and creating a foreign key for it:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
Creating a many-to-many relation
Let's create a many-to-one / many-to-many relation. Let's say a photo can be in many albums, and each album can contain many photos. Let's create an Album
class:
import {Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable} from "typeorm";
@Entity()
export class Album {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(type => Photo, photo => photo.albums)
@JoinTable()
photos: Photo[];
}
@JoinTable
is required to specify that this is the owner side of the relationship.
Now let's add the inverse side of our relation to the Photo
class:
export class Photo {
/// ... other columns
@ManyToMany(type => Album, album => album.photos)
albums: Album[];
}
After you run the application, the ORM will create a albumphotosphoto_albums junction table:
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
Don't forget to register the Album
class with your connection in the ORM:
const options: ConnectionOptions = {
// ... other options
entities: [Photo, PhotoMetadata, Author, Album]
};
Now let's insert albums and photos to our database:
let connection = await createConnection(options);
// create a few albums
let album1 = new Album();
album1.name = "Bears";
await connection.manager.save(album1);
let album2 = new Album();
album2.name = "Me";
await connection.manager.save(album2);
// create a few photos
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.albums = [album1, album2];
await connection.manager.save(photo);
// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await connection
.getRepository(Photo)
.findOne(1, { relations: ["albums"] });
loadedPhoto
will be equal to:
{
id: 1,
name: "Me and Bears",
description: "I am near polar bears",
filename: "photo-with-bears.jpg",
albums: [{
id: 1,
name: "Bears"
}, {
id: 2,
name: "Me"
}]
}
Using QueryBuilder
You can use QueryBuilder to build SQL queries of almost any complexity. For example, you can do this:
let photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
.innerJoinAndSelect("photo.metadata", "metadata")
.leftJoinAndSelect("photo.albums", "album")
.where("photo.isPublished = true")
.andWhere("(photo.name = :photoName OR photo.name = :bearName)")
.orderBy("photo.id", "DESC")
.skip(5)
.take(10)
.setParameters({ photoName: "My", bearName: "Mishka" })
.getMany();
This query selects all published photos with "My" or "Mishka" names. It will select results from position 5 (pagination offset), and will select only 10 results (pagination limit). The selection result will be ordered by id in descending order. The photo's albums will be left-joined and their metadata will be inner joined.
You'll use the query builder in your application a lot. Learn more about QueryBuilder here.
Samples
Take a look at the samples in sample for examples of usage.
There are a few repositories which you can clone and start with:
- Example how to use TypeORM with TypeScript
- Example how to use TypeORM with JavaScript
- Example how to use TypeORM with JavaScript and Babel
- Example how to use TypeORM with TypeScript and SystemJS in Browser
- Example how to use Express and TypeORM
- Example how to use Koa and TypeORM
- Example how to use TypeORM with MongoDB
- Example how to use TypeORM in a Cordova/PhoneGap app
- Example how to use TypeORM with an Ionic app
- Example how to use TypeORM with React Native
- Example how to use TypeORM with Electron using JavaScript
- Example how to use TypeORM with Electron using TypeScript
Extensions
There are several extensions that simplify working with TypeORM and integrating it with other modules:
- TypeORM + GraphQL framework
- TypeORM integration with TypeDI
- TypeORM integration with routing-controllers
- Models generation from existing database - typeorm-model-generator
Contributing
Learn about contribution here and how to setup your development environment here.
This project exists thanks to all the people who contribute:
Sponsors
Open source is hard and time-consuming. If you want to invest into TypeORM's future you can become a sponsor and make our core team to spend more time on TypeORM's improvements and new features. Become a sponsor
Gold Sponsors
Become a gold sponsor and get a premium technical support from our core contributors. Become a gold sponsor