Introducción
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
TypeORM es un ORM que puede ejecutarse en plataformas Node.js, Browser, Cordova, Ionic, React Native, NativeScript, Expo y Electron, y puede usarse con TypeScript y JavaScript (ES2021).
Su objetivo es siempre soportar las últimas características de JavaScript y proporcionar funcionalidades adicionales que te ayuden a desarrollar cualquier tipo de aplicación que utilice bases de datos, desde aplicaciones pequeñas con pocas tablas hasta aplicaciones empresariales a gran escala con múltiples bases de datos.
TypeORM soporta más bases de datos que cualquier otro ORM de JS/TS: Google Spanner, Microsoft SqlServer, MongoDB, MySQL/MariaDB, Oracle, Postgres, SAP HANA y SQLite, así como bases de datos derivadas y diferentes drivers.
TypeORM soporta tanto el patrón Active Record como Data Mapper, a diferencia de todos los demás ORMs de JavaScript existentes actualmente, lo que significa que puedes escribir aplicaciones de alta calidad, débilmente acopladas, escalables y mantenibles de la manera más productiva.
TypeORM está altamente influenciado por otros ORMs como Hibernate, Doctrine y Entity Framework.
Características
-
Soporta tanto DataMapper como ActiveRecord (a tu elección).
-
Entidades y columnas.
-
Tipos de columna específicos de cada base de datos.
-
Entity manager.
-
Repositorios y repositorios personalizados.
-
Modelo objeto-relacional limpio.
-
Asociaciones (relaciones).
-
Relaciones eager y lazy.
-
Relaciones unidireccionales, bidireccionales y autoreferenciadas.
-
Soporta múltiples patrones de herencia.
-
Cascadas.
-
Índices.
-
Transacciones.
-
Migraciones con generación automática.
-
Pool de conexiones.
-
Replicación.
-
Uso de múltiples instancias de base de datos.
-
Trabajo con múltiples tipos de bases de datos.
-
Consultas entre bases de datos y entre esquemas.
-
QueryBuilder con sintaxis elegante, flexible y potente.
-
Joins tipo left e inner.
-
Paginación adecuada para consultas con joins.
-
Caché de consultas.
-
Streaming de resultados en crudo.
-
Logging.
-
Listeners y subscribers (hooks).
-
Soporta el patrón closure table.
-
Declaración de esquemas en modelos o archivos de configuración separados.
-
Soporta MySQL / MariaDB / Postgres / CockroachDB / SQLite / Microsoft SQL Server / Oracle / SAP Hana / sql.js.
-
Soporta bases de datos NoSQL MongoDB.
-
Funciona en plataformas Node.js / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron.
-
Soporte para TypeScript y JavaScript.
-
Soporte para ESM y CommonJS.
-
Código generado con alto rendimiento, flexible, limpio y mantenible.
-
Sigue todas las mejores prácticas posibles.
-
CLI.
Y más...
Con TypeORM, tus modelos lucen así:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
age: number
}
Y tu lógica de dominio se ve así:
const userRepository = AppDataSource.getRepository(User)
const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await userRepository.save(user)
const allUsers = await userRepository.find()
const firstUser = await userRepository.findOneBy({
id: 1,
}) // find by id
const timber = await userRepository.findOneBy({
firstName: "Timber",
lastName: "Saw",
}) // find by firstName and lastName
await userRepository.remove(timber)
Alternativamente, si prefieres usar la implementación ActiveRecord, también puedes utilizarla:
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
}
Y tu lógica de dominio se verá de esta manera:
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.findOneBy({
id: 1,
})
const timber = await User.findOneBy({
firstName: "Timber",
lastName: "Saw",
})
await timber.remove()
Instalación
-
Instala el paquete npm:
npm install typeorm -
Necesitas instalar el shim
reflect-metadata:npm install reflect-metadatae importarlo en algún lugar global de tu aplicación (por ejemplo en
app.ts):import "reflect-metadata" -
Puede que necesites instalar las definiciones de tipos para Node:
npm install @types/node --save-dev -
Instala un driver de base de datos: consulta la documentación para cada driver específico: mongodb, mssql, mysql/mariadb, oracle, postgres, sap, spanner, sqlite.
Configuración de TypeScript
Además, asegúrate de usar TypeScript versión 4.5 o superior,
y de tener habilitadas las siguientes opciones en tsconfig.json:
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
Inicio Rápido
La forma más rápida de comenzar con TypeORM es usar sus comandos CLI para generar un proyecto inicial. El inicio rápido solo funciona si estás usando TypeORM en una aplicación Node.js. Si usas otras plataformas, continúa con la guía paso a paso.
Para crear un nuevo proyecto usando CLI, ejecuta el siguiente comando:
npx typeorm init --name MyProject --database postgres
Donde name es el nombre de tu proyecto y database es la base de datos que usarás.
La base de datos puede ser uno de los siguientes valores: mysql, mariadb, postgres, cockroachdb, sqlite, mssql, sap, spanner, oracle, mongodb,
cordova, react-native, expo, nativescript.
Este comando generará un nuevo proyecto en el directorio MyProject con los siguientes archivos:
MyProject
├── src // place of your TypeScript code
│ ├── entities // place where your entities (database models) are stored
│ │ └── User.ts // sample entity
│ ├── migrations // place where your migrations are stored
│ ├── data-source.ts // data source and all connection configuration
│ └── index.ts // start point of your application
├── .gitignore // standard gitignore file
├── package.json // node module dependencies
├── README.md // simple readme file
└── tsconfig.json // TypeScript compiler options
También puedes ejecutar
typeorm initen un proyecto node existente, pero ten cuidado: podría sobrescribir algunos archivos que ya tengas.
El siguiente paso es instalar las dependencias del nuevo proyecto:
cd MyProject
npm install
Después de instalar todas las dependencias, edita el archivo data-source.ts y configura tus propias opciones de conexión a la base de datos:
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "test",
password: "test",
database: "test",
synchronize: true,
logging: true,
entities: [Post, Category],
subscribers: [],
migrations: [],
})
En particular, la mayoría de las veces solo necesitarás configurar
host, username, password, database y quizás port.
Una vez que completes la configuración y todos los módulos node estén instalados, puedes ejecutar tu aplicación:
npm start
Eso es todo, tu aplicación debería ejecutarse correctamente e insertar un nuevo usuario en la base de datos. Puedes continuar trabajando con este proyecto, integrar otros módulos que necesites y comenzar a crear más entidades.
Puedes generar un proyecto ESM ejecutando:
npx typeorm init --name MyProject --database postgres --module esm
Puedes generar un proyecto aún más avanzado con express instalado ejecutando el comando:
npx typeorm init --name MyProject --database mysql --express
Puedes generar un archivo docker-compose ejecutando el comando
npx typeorm init --name MyProject --database postgres --docker.
Guía Paso a Paso
¿Qué esperas de un ORM? Primero, esperas que cree tablas de base de datos por ti y encuentre/inserte/actualice/elimine tus datos sin el dolor de escribir muchas consultas SQL difíciles de mantener. Esta guía te mostrará cómo configurar TypeORM desde cero y hacer que cumpla lo que esperas de un ORM.
Crear un modelo
Trabajar con una base de datos comienza creando tablas. ¿Cómo le dices a TypeORM que cree una tabla de base de datos? La respuesta es: mediante los modelos. Tus modelos en tu aplicación son tus tablas de base de datos.
Por ejemplo, tienes un modelo Photo:
export class Photo {
id: number
name: string
description: string
filename: string
views: number
isPublished: boolean
}
Y quieres almacenar fotos en tu base de datos. Para almacenar cosas en la base de datos, primero necesitas una tabla, y las tablas se crean a partir de tus modelos. No todos los modelos, solo aquellos que definas como entidades.
Crear una entidad
Una entidad es tu modelo decorado con el decorador @Entity.
Se creará una tabla de base de datos para esos modelos.
Trabajarás con entidades en todas partes de TypeORM.
Puedes cargarlas/insertarlas/actualizarlas/eliminarlas y realizar otras operaciones con ellas.
Hagamos de nuestro modelo Photo una entidad:
import { Entity } from "typeorm"
@Entity()
export class Photo {
id: number
name: string
description: string
filename: string
views: number
isPublished: boolean
}
Ahora se creará una tabla de base de datos para la entidad Photo, y podremos trabajar con ella en cualquier parte de nuestra aplicación.
Hemos creado una tabla, pero ¿qué tabla puede existir sin columnas?
Agreguemos algunas columnas a nuestra tabla.
Agregar columnas a la tabla
Para agregar columnas de base de datos, decora las propiedades de la entidad que quieres convertir en columna
con el decorador @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
}
Ahora se agregarán las columnas id, name, description, filename, views e isPublished a la tabla photo.
Los tipos de columna en la base de datos se infieren de los tipos de propiedad que usaste, ej.
number se convertirá en integer, string en varchar, boolean en bool, etc.
Pero puedes usar cualquier tipo de columna que soporte tu base de datos especificándolo explícitamente en el decorador @Column.
Generamos una tabla con columnas, pero falta algo. Cada tabla debe tener una columna con una clave primaria.
Crear una columna primaria
Cada entidad debe tener al menos una columna de clave primaria.
Es un requisito obligatorio.
Para hacer que una columna sea clave primaria, usa el decorador @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
}
Crear una columna auto-generada
Ahora, supongamos que quieres que tu columna id sea auto-generada (esto se conoce como columna auto-incremental/secuencial/serial/identidad generada).
Para hacerlo, cambia el decorador @PrimaryColumn por @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
}
Tipos de datos de columna
A continuación, ajustemos los tipos de datos. Por defecto, string se mapea a un tipo similar a varchar(255) (dependiendo de la base de datos). number se mapea a un tipo similar a integer (dependiendo de la base de datos). No queremos que todas nuestras columnas estén limitadas a varchars o integers. Configuremos los tipos de datos correctos:
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
}
Los tipos de columna son específicos de cada base de datos. Puedes usar cualquier tipo que soporte tu base de datos. Más información sobre tipos soportados aquí.
Crear un nuevo DataSource
Ahora que nuestra entidad está creada, creemos el archivo index.ts y configuremos nuestro DataSource:
import "reflect-metadata"
import { DataSource } from "typeorm"
import { Photo } from "./entity/Photo"
const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "root",
password: "admin",
database: "test",
entities: [Photo],
synchronize: true,
logging: false,
})
// to initialize the initial connection with the database, register all entities
// and "synchronize" database schema, call "initialize()" method of a newly created database
// once in your application bootstrap
try {
await AppDataSource.initialize()
} catch (error) {
console.log(error)
}
En este ejemplo estamos usando Postgres, pero puedes utilizar cualquier otra base de datos compatible.
Para usar otra base de datos, cambia el type en las opciones por el tipo de base de datos que estés utilizando:
mysql, mariadb, postgres, cockroachdb, sqlite, mssql, oracle, sap, spanner, cordova, nativescript, react-native,
expo, o mongodb.
También asegúrate de usar tus propios ajustes de host, puerto, usuario, contraseña y base de datos.
Hemos añadido nuestra entidad Photo a la lista de entidades para este origen de datos. Cada entidad que utilices en tu conexión debe estar listada allí.
Configurar synchronize garantiza que tus entidades se sincronicen con la base de datos cada vez que ejecutes la aplicación.
Ejecutando la aplicación
Ahora si ejecutas tu index.ts, se inicializará una conexión con la base de datos y se creará una tabla para tus fotos.
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(100) | |
| description | text | |
| filename | varchar(255) | |
| views | int | |
| isPublished | boolean | |
+-------------+--------------+----------------------------+
Creando e insertando una foto en la base de datos
Ahora creemos una nueva foto para guardarla en la base de datos:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const 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 AppDataSource.manager.save(photo)
console.log("Photo has been saved. Photo id is", photo.id)
Una vez guardada tu entidad, obtendrá un id recién generado.
El método save devuelve una instancia del mismo objeto que le pasaste.
No es una nueva copia del objeto, sino que modifica su "id" y lo devuelve.
Usando Entity Manager
Acabamos de crear una nueva foto y guardarla en la base de datos.
Usamos EntityManager para guardarla.
Con el entity manager, puedes manipular cualquier entidad en tu aplicación.
Por ejemplo, carguemos nuestra entidad guardada:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const savedPhotos = await AppDataSource.manager.find(Photo)
console.log("All photos from the db: ", savedPhotos)
savedPhotos será un array de objetos Photo con los datos cargados desde la base de datos.
Aprende más sobre EntityManager.
Usando Repositorios
Ahora refactoricemos nuestro código y usemos Repository en lugar de EntityManager.
Cada entidad tiene su propio repositorio que maneja todas las operaciones con dicha entidad.
Cuando trabajas mucho con entidades, los Repositorios son más convenientes que los EntityManagers:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const 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
const photoRepository = AppDataSource.getRepository(Photo)
await photoRepository.save(photo)
console.log("Photo has been saved")
const savedPhotos = await photoRepository.find()
console.log("All photos from the db: ", savedPhotos)
Aprende más sobre los Repositorios aquí.
Cargando desde la base de datos
Probemos más operaciones de carga usando el Repositorio:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const allPhotos = await photoRepository.find()
console.log("All photos from the db: ", allPhotos)
const firstPhoto = await photoRepository.findOneBy({
id: 1,
})
console.log("First photo from the db: ", firstPhoto)
const meAndBearsPhoto = await photoRepository.findOneBy({
name: "Me and Bears",
})
console.log("Me and Bears photo from the db: ", meAndBearsPhoto)
const allViewedPhotos = await photoRepository.findBy({ views: 1 })
console.log("All viewed photos: ", allViewedPhotos)
const allPublishedPhotos = await photoRepository.findBy({ isPublished: true })
console.log("All published photos: ", allPublishedPhotos)
const [photos, photosCount] = await photoRepository.findAndCount()
console.log("All photos: ", photos)
console.log("Photos count: ", photosCount)
Actualizando en la base de datos
Ahora carguemos una sola foto desde la base de datos, actualicémosla y guardémosla:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const photoToUpdate = await photoRepository.findOneBy({
id: 1,
})
photoToUpdate.name = "Me, my friends and polar bears"
await photoRepository.save(photoToUpdate)
Ahora la foto con id = 1 se actualizará en la base de datos.
Eliminando de la base de datos
Ahora eliminemos nuestra foto de la base de datos:
import { Photo } from "./entity/Photo"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const photoToRemove = await photoRepository.findOneBy({
id: 1,
})
await photoRepository.remove(photoToRemove)
Ahora la foto con id = 1 se eliminará de la base de datos.
Creando una relación uno-a-uno
Creemos una relación uno-a-uno con otra clase.
Creemos una nueva clase en PhotoMetadata.ts. Esta clase PhotoMetadata contendrá metainformación adicional de nuestra foto:
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(() => Photo)
@JoinColumn()
photo: Photo
}
Aquí estamos usando un nuevo decorador llamado @OneToOne. Nos permite crear una relación uno-a-uno entre dos entidades. También añadimos el decorador @JoinColumn, que indica que este lado de la relación será el propietario.
Las relaciones pueden ser unidireccionales o bidireccionales.
Solo un lado de la relación puede ser el propietario.
El decorador @JoinColumn es obligatorio en el lado propietario de la relación.
Si ejecutas la aplicación, verás una nueva tabla generada que contendrá una columna con una clave foránea para la relación con la foto:
+-------------+--------------+----------------------------+
| photo_metadata |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| height | int | |
| width | int | |
| comment | varchar(255) | |
| compressed | boolean | |
| orientation | varchar(255) | |
| photoId | int | FOREIGN KEY |
+-------------+--------------+----------------------------+
Guardando una relación uno-a-uno
Ahora guardemos una foto junto con sus metadatos y asociémoslos entre sí.
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
// Create a photo
const 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
// Create a photo metadata
const metadata = new PhotoMetadata()
metadata.height = 640
metadata.width = 480
metadata.compressed = true
metadata.comment = "cybershoot"
metadata.orientation = "portrait"
metadata.photo = photo // this way we connect them
// Get entity repositories
const photoRepository = AppDataSource.getRepository(Photo)
const metadataRepository = AppDataSource.getRepository(PhotoMetadata)
// First we should save a photo
await photoRepository.save(photo)
// The Photo is saved. Now we need to save a photo metadata
await metadataRepository.save(metadata)
// Done
console.log(
"Metadata is saved, and the relation between metadata and photo is created in the database too",
)
Lado inverso de la relación
Las relaciones pueden ser unidireccionales o bidireccionales. Actualmente, nuestra relación entre PhotoMetadata y Photo es unidireccional. El propietario de la relación es PhotoMetadata, y Photo no sabe nada sobre PhotoMetadata. Esto complica el acceso a PhotoMetadata desde el lado de Photo. Para solucionar este problema, debemos agregar una relación inversa y hacer que las relaciones entre PhotoMetadata y Photo sean bidireccionales. Modifiquemos nuestras entidades:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(() => 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(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: PhotoMetadata
}
photo => photo.metadata es una función que devuelve el nombre del lado inverso de la relación.
Aquí mostramos que la propiedad metadata de la clase Photo es donde almacenamos PhotoMetadata en la clase Photo.
En lugar de pasar una función que devuelve una propiedad de la foto, también podrías pasar una cadena al decorador @OneToOne, como "metadata".
Pero usamos este enfoque de tipo función para facilitar la refactorización.
Ten en cuenta que debemos usar el decorador @JoinColumn solo en un lado de una relación.
El lado en el que coloques este decorador será el lado propietario de la relación.
El lado propietario de una relación contiene una columna con una clave foránea en la base de datos.
Relaciones en proyectos ESM
Si usas ESM en tu proyecto TypeScript, deberías usar el tipo wrapper Relation en las propiedades de relación para evitar problemas de dependencias circulares.
Modifiquemos nuestras entidades:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
Relation,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class PhotoMetadata {
/* ... other columns */
@OneToOne(() => Photo, (photo) => photo.metadata)
@JoinColumn()
photo: Relation<Photo>
}
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
Relation,
} from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
@Entity()
export class Photo {
/* ... other columns */
@OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
metadata: Relation<PhotoMetadata>
}
Cargando objetos con sus relaciones
Ahora carguemos nuestra foto y sus metadatos en una sola consulta.
Hay dos formas de hacerlo: usando métodos find* o usando la funcionalidad de QueryBuilder.
Primero usemos el método find*.
Los métodos find* te permiten especificar un objeto con la interfaz FindOneOptions / FindManyOptions.
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
import { AppDataSource } from "./index"
const photoRepository = AppDataSource.getRepository(Photo)
const photos = await photoRepository.find({
relations: {
metadata: true,
},
})
Aquí, photos contendrá un array de fotos de la base de datos, y cada foto contendrá sus metadatos. Aprende más sobre las opciones de búsqueda (Find Options) en esta documentación.
Usar opciones de búsqueda es bueno y muy simple, pero si necesitas una consulta más compleja, deberías usar QueryBuilder en su lugar.
QueryBuilder permite usar consultas más complejas de manera elegante:
import { Photo } from "./entity/Photo"
import { PhotoMetadata } from "./entity/PhotoMetadata"
import { AppDataSource } from "./index"
const photos = await AppDataSource.getRepository(Photo)
.createQueryBuilder("photo")
.innerJoinAndSelect("photo.metadata", "metadata")
.getMany()
QueryBuilder permite la creación y ejecución de consultas SQL de casi cualquier complejidad.
Cuando trabajas con QueryBuilder, piensa como si estuvieras creando una consulta SQL.
En este ejemplo, "photo" y "metadata" son alias aplicados a las fotos seleccionadas.
Usas alias para acceder a columnas y propiedades de los datos seleccionados.
Usando cascadas para guardar objetos relacionados automáticamente
Podemos configurar opciones de cascada en nuestras relaciones, en los casos en que queremos que nuestro objeto relacionado se guarde cada vez que se guarde el otro objeto.
Cambiemos un poco el decorador @OneToOne de nuestra foto:
export class Photo {
// ... other columns
@OneToOne(() => PhotoMetadata, (metadata) => metadata.photo, {
cascade: true,
})
metadata: PhotoMetadata
}
Usar cascade nos permite no guardar por separado las fotos y por separado los objetos de metadatos.
Ahora podemos simplemente guardar un objeto foto, y el objeto de metadatos se guardará automáticamente debido a las opciones de cascada.
import { AppDataSource } from "./index"
// create photo object
const 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
const metadata = new PhotoMetadata()
metadata.height = 640
metadata.width = 480
metadata.compressed = true
metadata.comment = "cybershoot"
metadata.orientation = "portrait"
photo.metadata = metadata // this way we connect them
// get repository
const photoRepository = AppDataSource.getRepository(Photo)
// saving a photo also save the metadata
await photoRepository.save(photo)
console.log("Photo is saved, photo metadata is saved too.")
Observa que ahora establecemos la propiedad metadata de la foto, en lugar de la propiedad photo de los metadatos como antes.
La característica cascade solo funciona si conectas la foto a sus metadatos desde el lado de la foto.
Si estableces el lado de los metadatos, los metadatos no se guardarían automáticamente.
Creando una relación muchos-a-uno / uno-a-muchos
Creemos una relación muchos-a-uno/uno-a-muchos.
Digamos que una foto tiene un autor, y cada autor puede tener muchas fotos.
Primero, creemos una clase Author:
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToMany,
JoinColumn,
} from "typeorm"
import { Photo } from "./Photo"
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany(() => Photo, (photo) => photo.author) // note: we will create author property in the Photo class below
photos: Photo[]
}
Author contiene el lado inverso de una relación.
OneToMany siempre es el lado inverso de la relación, y no puede existir sin ManyToOne en el otro lado de la relación.
Ahora agreguemos el lado propietario de la relación en la entidad Photo:
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from "typeorm"
import { PhotoMetadata } from "./PhotoMetadata"
import { Author } from "./Author"
@Entity()
export class Photo {
/* ... other columns */
@ManyToOne(() => Author, (author) => author.photos)
author: Author
}
En relaciones muchos-a-uno / uno-a-muchos, el lado propietario es siempre muchos-a-uno.
Significa que la clase que usa @ManyToOne almacenará el id del objeto relacionado.
Después de ejecutar la aplicación, el ORM creará la tabla author:
+-------------+--------------+----------------------------+
| author |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
También modificará la tabla photo, añadiendo una nueva columna author y creando una clave foránea para ella:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| description | varchar(255) | |
| filename | varchar(255) | |
| isPublished | boolean | |
| authorId | int | FOREIGN KEY |
+-------------+--------------+----------------------------+
Creando una relación de muchos a muchos
Vamos a crear una relación de muchos a muchos. Supongamos que una foto puede estar en muchos álbumes y cada álbum puede contener muchas fotos. Creemos una clase Album:
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
@Entity()
export class Album {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Photo, (photo) => photo.albums)
@JoinTable()
photos: Photo[]
}
@JoinTable es necesario para especificar que este es el lado propietario de la relación.
Ahora agreguemos el lado inverso de nuestra relación a la clase Photo:
export class Photo {
// ... other columns
@ManyToMany(() => Album, (album) => album.photos)
albums: Album[]
}
Después de ejecutar la aplicación, el ORM creará una tabla de unión album_photos_photo_albums:
+-------------+--------------+----------------------------+
| album_photos_photo_albums |
+-------------+--------------+----------------------------+
| album_id | int | PRIMARY KEY FOREIGN KEY |
| photo_id | int | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
Recuerda registrar la clase Album con tu conexión en el ORM:
const options: DataSourceOptions = {
// ... other options
entities: [Photo, PhotoMetadata, Author, Album],
}
Ahora insertemos álbumes y fotos en nuestra base de datos:
import { AppDataSource } from "./index"
// create a few albums
const album1 = new Album()
album1.name = "Bears"
await AppDataSource.manager.save(album1)
const album2 = new Album()
album2.name = "Me"
await AppDataSource.manager.save(album2)
// create a few photos
const 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
photo.albums = [album1, album2]
await AppDataSource.manager.save(photo)
// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await AppDataSource.getRepository(Photo).findOne({
where: {
id: 1,
},
relations: {
albums: true,
},
})
loadedPhoto será igual a:
{
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"
}]
}
Usando QueryBuilder
Puedes usar QueryBuilder para construir consultas SQL de casi cualquier complejidad. Por ejemplo, puedes hacer esto:
const photos = await AppDataSource.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()
Esta consulta selecciona todas las fotos publicadas con nombres "My" o "Mishka". Seleccionará resultados desde la posición 5 (offset de paginación) y tomará solo 10 resultados (límite de paginación). Los resultados se ordenarán por id en orden descendente. Los álbumes de fotos tendrán un left join y sus metadatos tendrán un inner join.
Usarás el query builder frecuentemente en tu aplicación. Aprende más sobre QueryBuilder aquí.
Ejemplos
Revisa los ejemplos en sample para ver casos de uso.
Hay varios repositorios que puedes clonar y comenzar a utilizar:
-
Ejemplo de uso de TypeORM con TypeScript y SystemJS en navegador
-
Ejemplo de uso de TypeORM con TypeScript y React en navegador
Extensiones
Existen varias extensiones que simplifican el trabajo con TypeORM y su integración con otros módulos:
-
Generación de modelos desde una base de datos existente - typeorm-model-generator
-
Cargador de fixtures - typeorm-fixtures-cli
-
Generador de diagramas ER - typeorm-uml
-
Otro generador de diagramas ER - erdia
-
Crear, eliminar y poblar bases de datos - typeorm-extension
-
Actualización automática de
data-source.tsdespués de generar migrations/entidades - typeorm-codebase-sync -
Manipulación sencilla de objetos
relations- typeorm-relations -
Generación automática de
relationsbasada en consultas GraphQL - typeorm-relations-graphql
Contribución
Aprende sobre contribución aquí y cómo configurar tu entorno de desarrollo aquí.
Este proyecto existe gracias a todas las personas que contribuyen:
Patrocinadores
El código abierto requiere mucho esfuerzo y tiempo. Si quieres invertir en el futuro de TypeORM, puedes convertirte en patrocinador y permitir que nuestro equipo central dedique más tiempo a mejoras y nuevas funcionalidades. Conviértete en patrocinador
Patrocinadores Oro
Conviértete en patrocinador oro y obtén soporte técnico premium de nuestros contribuidores principales. Conviértete en patrocinador oro