10 TypeORM Interview Questions and Answers
Prepare for your next technical interview with this guide on TypeORM, featuring common questions and answers to enhance your database management skills.
Prepare for your next technical interview with this guide on TypeORM, featuring common questions and answers to enhance your database management skills.
TypeORM is a powerful and flexible Object-Relational Mapper (ORM) for TypeScript and JavaScript. It simplifies database interactions by allowing developers to work with database entities as if they were regular TypeScript or JavaScript objects. TypeORM supports multiple database systems, including MySQL, PostgreSQL, SQLite, and more, making it a versatile choice for various projects.
This article provides a curated selection of TypeORM interview questions designed to help you demonstrate your proficiency and understanding of this ORM. By reviewing these questions and their answers, you can better prepare for technical interviews and showcase your ability to effectively manage database operations using TypeORM.
User
with fields id
, name
, and email
.To define a basic entity in TypeORM for a User
with fields id
, name
, and email
, you can use the following code snippet. This example uses TypeScript, which is commonly used with TypeORM.
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
In TypeORM, a one-to-many relationship is established between two entities when one entity can be associated with multiple instances of another entity. This is typically done using the @OneToMany
and @ManyToOne
decorators. The @OneToMany
decorator is used on the entity that holds the collection, while the @ManyToOne
decorator is used on the entity that holds the reference to the single instance.
Example:
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => Post, post => post.user) posts: Post[]; } @Entity() export class Post { @PrimaryGeneratedColumn() id: number; @Column() title: string; @ManyToOne(() => User, user => user.posts) user: User; }
In this example, the User
entity has a one-to-many relationship with the Post
entity. The User
entity uses the @OneToMany
decorator to indicate that it can have multiple posts, while the Post
entity uses the @ManyToOne
decorator to indicate that each post belongs to a single user.
To find all users whose name starts with ‘A’ using TypeORM, you can utilize the find
method along with a where
clause.
Here is an example of how you can achieve this:
import { getRepository } from 'typeorm'; import { User } from './entity/User'; async function findUsersStartingWithA() { const userRepository = getRepository(User); const users = await userRepository.find({ where: { name: Like('A%') } }); return users; }
In this example, the Like
operator is used to match any user whose name starts with ‘A’. The getRepository
function is used to get the repository for the User
entity, and the find
method is used to perform the query.
age
to the User
entity.To add a new column age
to the User
entity, you would create a migration script. Below is an example of how to achieve this:
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; export class AddAgeColumnToUser1634567890123 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.addColumn("user", new TableColumn({ name: "age", type: "int", isNullable: true })); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropColumn("user", "age"); } }
In this example, the up
method is used to define the changes to be applied to the database, which in this case is adding a new column age
to the user
table. The down
method is used to revert these changes, ensuring that the migration can be rolled back if necessary.
In TypeORM, transactions are used to ensure that a series of database operations are executed in a way that maintains data integrity.
Here is an example of how to handle transactions in TypeORM:
import { getManager } from "typeorm"; async function transactionalOperation() { await getManager().transaction(async transactionalEntityManager => { // Perform your database operations here await transactionalEntityManager.save(entity1); await transactionalEntityManager.save(entity2); // Add more operations as needed }); }
In this example, the transaction
method of getManager
is used to execute a series of operations within a transaction. If any of the operations fail, the entire transaction is rolled back.
Custom repositories in TypeORM allow you to extend the default repository functionality by adding custom methods and encapsulating complex query logic. This is particularly useful when you have specific business logic that needs to be reused across different parts of your application.
To create a custom repository, you need to extend the Repository
class and define your custom methods. You can then use this custom repository in your services or controllers.
Example:
import { EntityRepository, Repository } from 'typeorm'; import { User } from '../entity/User'; @EntityRepository(User) export class UserRepository extends Repository<User> { findByName(firstName: string, lastName: string) { return this.findOne({ where: { firstName, lastName } }); } } // Usage in a service import { getCustomRepository } from 'typeorm'; import { UserRepository } from '../repository/UserRepository'; const userRepository = getCustomRepository(UserRepository); const user = await userRepository.findByName('John', 'Doe');
To create a custom repository method to find users by their email domain, you can extend the base repository and add a method that uses a query to filter users based on the email domain.
Example:
import { EntityRepository, Repository } from 'typeorm'; import { User } from './user.entity'; @EntityRepository(User) export class UserRepository extends Repository<User> { async findByEmailDomain(domain: string): Promise<User[]> { return this.createQueryBuilder('user') .where('user.email LIKE :domain', { domain: `%@${domain}` }) .getMany(); } }
In this example, the findByEmailDomain
method constructs a query to find users whose email addresses end with the specified domain. The LIKE
operator is used to match the email pattern.
To use TypeORM with a NestJS application, you need to follow these steps:
1. Install the necessary packages.
2. Configure the TypeORM module.
3. Create an entity.
4. Use a repository to perform database operations.
First, install the required packages:
npm install @nestjs/typeorm typeorm mysql2
Next, configure the TypeORM module in your app.module.ts
:
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { User } from './user.entity'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [User], synchronize: true, }), TypeOrmModule.forFeature([User]), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
Create an entity user.entity.ts
:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() age: number; }
Finally, use a repository to perform database operations in a service:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; @Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} async createUser(name: string, age: number): Promise<User> { const user = this.userRepository.create({ name, age }); return this.userRepository.save(user); } async findAll(): Promise<User[]> { return this.userRepository.find(); } }
Decorators in TypeORM are used to define the structure of the database schema directly within the entity classes. They provide a way to annotate classes and their properties to specify how they should be mapped to the database. This approach makes the code more readable and maintainable.
For example, the @Entity
decorator is used to mark a class as a database table, while the @Column
decorator is used to mark a property as a table column. Other decorators like @PrimaryGeneratedColumn
and @ManyToOne
are used to define primary keys and relationships between tables, respectively.
Example:
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; @Entity() class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @ManyToOne(() => Role, role => role.users) role: Role; } @Entity() class Role { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => User, user => user.role) users: User[]; }
In this example, the User
class is marked as an entity with the @Entity
decorator. The id
property is marked as a primary key with the @PrimaryGeneratedColumn
decorator, and the name
property is marked as a column with the @Column
decorator. The role
property is marked with the @ManyToOne
decorator to indicate a many-to-one relationship with the Role
entity.
Testing TypeORM entities and repositories involves several steps to ensure that your database interactions are functioning correctly. The process generally includes setting up a test database, configuring TypeORM to use this test database, and writing unit tests using a testing framework like Jest.
First, you need to configure a test database. This can be an in-memory database like SQLite for faster tests or a separate instance of your actual database. You will then configure TypeORM to connect to this test database during testing.
Next, you write unit tests for your entities and repositories. These tests should cover CRUD operations and any custom queries or methods you have implemented. Using Jest, you can mock dependencies and focus on testing the logic within your repositories.
Example:
import { createConnection, getRepository } from 'typeorm'; import { User } from './entities/User'; import { UserRepository } from './repositories/UserRepository'; beforeAll(async () => { await createConnection({ type: 'sqlite', database: ':memory:', dropSchema: true, entities: [User], synchronize: true, logging: false, }); }); afterAll(async () => { const connection = getConnection(); await connection.close(); }); test('should create a new user', async () => { const userRepository = getRepository(User); const user = new User(); user.name = 'John Doe'; user.email = '[email protected]'; await userRepository.save(user); const savedUser = await userRepository.findOne({ email: '[email protected]' }); expect(savedUser).toBeDefined(); expect(savedUser.name).toBe('John Doe'); });