Querying With Repositories & QueryBuilder

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.

4 min read Level 2/5 #nestjs#typeorm#queries
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. :active with the params object, never string concatenation — that’s how SQL injection happens.
  • Use leftJoinAndSelect to populate the relation, plain leftJoin to filter without hydrating.
  • .getMany() returns entities, .getRawMany() returns plain rows. Pick raw when you only want aggregates.
Relations & Eager Loading →