NestJS — Enterprise Node.js

Repositories, Relations & Querying

18 min Lesson 21 of 48

Repositories, Relations & Querying

A repository is TypeORM's interface for reading and writing one entity. NestJS injects it for you, and you call its methods from a service. This lesson covers everyday CRUD, defining relations between entities, and building more advanced queries.

Injecting a repository

Use @InjectRepository() to get a typed repository for an entity registered with forFeature():

import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private readonly users: Repository<User>, ) {} }

Everyday CRUD

// CREATE const user = this.users.create({ name: 'Sara', email: 's@x.com' }); await this.users.save(user); // READ await this.users.find(); // all await this.users.findOne({ where: { id: 1 } }); // one await this.users.findBy({ isActive: true }); // filtered // UPDATE await this.users.update(1, { name: 'Sara A.' }); // DELETE await this.users.delete(1);
create() vs save(): create() only builds an entity instance in memory (and runs default values) — it does not touch the database. save() is what persists it. Forgetting save() is a classic "why isn't it saving?" bug.

Defining relations

Relations connect entities. TypeORM provides decorators for each cardinality. A one-to-many / many-to-one pair, for example a user with many posts:

@Entity() export class User { @OneToMany(() => Post, (post) => post.author) posts: Post[]; } @Entity() export class Post { @ManyToOne(() => User, (user) => user.posts) author: User; }

Other relation decorators are @OneToOne() and @ManyToMany() (the latter creates a join table automatically).

Loading relations

Relations are not loaded by default — you ask for them with the relations option:

await this.users.findOne({ where: { id: 1 }, relations: { posts: true }, // eager-load this user's posts });
Beware the N+1 query problem. Loading a list of users and then accessing each one's posts in a loop can fire one query per user. Load the relation up front (with relations or a JOIN in the query builder) instead of lazily inside a loop.

The query builder for complex queries

For anything beyond simple finds — joins, aggregates, conditional filters — use the query builder:

const result = await this.users .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .where('user.isActive = :active', { active: true }) .orderBy('user.createdAt', 'DESC') .take(10) .getMany();
Always use parameter binding (:active), never string concatenation. The query builder parameterises values, which prevents SQL injection. Building a WHERE clause by concatenating user input is a serious vulnerability.

Summary

Inject a Repository<Entity> with @InjectRepository() and use create/save/find/update/delete for CRUD (remember save() persists). Define relations with @OneToMany/@ManyToOne/@ManyToMany, load them with the relations option, and avoid N+1 queries. Use the query builder with bound parameters for complex, safe queries. Next: managing schema changes with migrations.