Simple Finds For 90%, QueryBuilder For the Rest
Querying With Repositories & QueryBuilder
Repositories cover most queries. Drop into QueryBuilder when you need joins, complex conditions, or hand-written SQL.
What you'll learn
- Use repo.find / findOne / save / delete
- Filter with where clauses
- Use QueryBuilder for joins
The repository API handles the queries you’ll write nine times out of ten.
For everything else, TypeORM has a QueryBuilder that lets you compose SQL
piece by piece.
The Repository API
// Read
await this.repo.find(); // all rows
await this.repo.findOne({ where: { id } }); // one or null
await this.repo.findBy({ active: true }); // shorthand
// Filter / sort / paginate
await this.repo.find({
where: { active: true },
order: { createdAt: 'DESC' },
take: 10,
skip: 20,
});
// Write
await this.repo.save({ email: 'a@b.com', name: 'A' });
await this.repo.update(id, { name: 'New' });
await this.repo.delete(id); save is upsert-ish: if the entity has an id, it updates; otherwise it
inserts. update skips entity hooks and is faster for bulk patches.
Operators for Complex Where Clauses
Plain object equality is fine for simple filters. For ranges and OR clauses, use the operator helpers:
import { LessThan, In, ILike, Not } from 'typeorm';
await this.repo.find({
where: {
createdAt: LessThan(new Date('2026-01-01')),
role: In(['admin', 'editor']),
email: ILike('%@example.com'),
deletedAt: Not(null),
},
}); You can pass an array of where clauses to get an SQL OR:
await this.repo.find({
where: [{ email: 'a@b.com' }, { name: 'A' }],
}); QueryBuilder For Joins & Custom SQL
When the where-object grammar runs out, switch to QueryBuilder. It’s more verbose but lets you express almost anything.
const users = await this.repo
.createQueryBuilder('u')
.leftJoinAndSelect('u.posts', 'p')
.where('u.active = :active', { active: true })
.andWhere('p.publishedAt IS NOT NULL')
.orderBy('u.createdAt', 'DESC')
.limit(20)
.getMany(); A few QueryBuilder habits worth picking up:
- Always parameterize.
:activewith the params object, never string concatenation — that’s how SQL injection happens. - Use
leftJoinAndSelectto populate the relation, plainleftJointo filter without hydrating. .getMany()returns entities,.getRawMany()returns plain rows. Pick raw when you only want aggregates.