Skip to main content

Repositories

Introduction

For DB operations, we use Repository Design Pattern. We have added some pre-defined methods, which can be used to perform your basic and day to day operations very easily.

In Repository Contract, we have added the declaration of all the methods which will be used to create custom repositories for the model specific repositories.

Create Repository

Before starting to use Repository, we need to create it first. Obeying SOLID principles, we need to create Contract and DatabaseRepository for each model.

note

Each model need to have its two files, One Contract and One Database Repository Implementation

To create UserRepositoryContract interface, simply do

src/user/repositories/user/contract.ts
import { User$Model } from "@app/_common";
import { RepositoryContract } from "@libs/core";

export interface UserRepositoryContract
extends RepositoryContract<User$Model> {}

Now, we need to create UserRepository DB class which will implement UserRepositoryContract interface.

src/user/repositories/user/database.ts
import { UserModel } from "../../models";
import { Injectable } from "@nestjs/common";
import { DatabaseRepository as DB, InjectModel } from "@libs/core";
import { UserRepositoryContract } from "./contract";
import { User$Model } from "@app/_common";

@Injectable()
export class UserRepository
extends DB<User$Model>
implements UserRepositoryContract
{
@InjectModel(UserModel)
model: UserModel;
}

To inject UserModel, use InjectModel decorator

info

DatabaseRepository contains all the methods which are defined in Available Methods section

Using Repository

Now, to use the repository we created, include the DB Repository Classes in the providers section in the module.

src/user/module.ts
import { Module, HttpModule } from "@nestjs/common";
import { UserService } from "./services";
import { USER_REPOSITORY } from "./constants";
import { UserRepository } from "./repositories";

@Module({
imports: [HttpModule],
controllers: [],
providers: [{ provide: USER_REPOSITORY, useClass: UserRepository }],
})
export class UserModule {}

Now, to inject the repo, you can do

src/user/services/User.ts
import { Injectable, Inject } from "@nestjs/common";
import { UserRepositoryContract } from "../repositories";
import { USER_REPOSITORY } from "../constants";

@Injectable()
export class UserService {
constructor(@Inject(USER_REPOSITORY) private users: UserRepositoryContract) {}
}

Repository Query Methods

info

The methods are available in src/core/db/repositories/Database.ts file

all()

Get all models from table.

/**
* @returns User[]
*/
const users = await this.users.all(); // returns User[]

firstWhere()

Get the first model with the matching criterias. If not found, it will throw an ModelNotFoundException exception. If you don't want to throw exception, pass false as second parameter.

Parameters:

ParameterRequired?DescriptionDefault
inputsYWhere condition that is to be addedundefined
errorNThrow exception if model is not foundtrue
/**
* Returns User if found, else throws ModelNotFoundException
*/
const users = await this.users.firstWhere(
{ contactNumber: "XXXXXXXXXX" },
true
);

/**
* Returns User if found, else throws ModelNotFoundException
*/
const users = await this.users.firstWhere(
{ contactNumber: "XXXXXXXXXX" },
false
);

getWhere()

Get all models with the matching criterias. If not found, it will throw an ModelNotFoundException exception. If you don't want to throw exception, pass false as second parameter.

Parameters:

ParameterRequired?DescriptionDefault
inputsYWhere condition that is to be added--
errorNThrow exception if model is not foundtrue
/**
* Returns User if found, else throws ModelNotFoundException
*/
const users = await this.users.getWhere({ contactNumber: "XXXXXXXXXX" }, true);

/**
* Returns User if found, else throws ModelNotFoundException
*/
const users = await this.users.getWhere({ contactNumber: "XXXXXXXXXX" }, false);

create()

Create the model in DB and return it's model equivalent instance.

Parameters:

ParameterRequired?DescriptionDefault
inputsYColumn values as object--
/**
* Create a new model with given inputs
*/
const user = await this.users.create({ firstName: "Tony", lastName: "Stark" });

createOrUpdate()

Update or create a model with given condition and values.

Parameters:

ParameterRequired?DescriptionDefault
conditionYExistence of any model is checked using the object--
valuesYIf the model is not found, values will be used to add columns--
/**
* Update or Create model with given condition and values
*/
const user = await this.users.createOrUpdate(
{ contactNumber: "XXXXXXXXXX" },
{ firstName: "Tony", lastName: "Stark" }
);

If any user with contact number as 'XXXXXXXXXX' exists in the system, it will return the same model. If not, then a new model will be created.

firstOrNew()

First or Create model with given condition and values

Parameters:

ParameterRequired?DescriptionDefault
conditionYExistence of any model is checked using the object--
valuesYIf the model is not found, values will be used to add columns--
/**
* Update or Create model with given condition and values
*/
const user = await this.users.firstOrNew(
{ contactNumber: "XXXXXXXXXX" },
{ firstName: "Tony", lastName: "Stark" }
);

If any user with contact number as 'XXXXXXXXXX' exists in the system, it will return the first model. If not, then a new model will be created.

update()

Update the given model with values.

Parameters:

ParameterRequired?DescriptionDefault
modelYTo update model--
valuesYColumns to update in the model--
const users = await this.users.firstWhere({ contactNumber: "XXXXXXXXXX" });
await this.users.update(user, { firstName: "New Name" });

New first name of the user will be, 'New Name'.

updateWhere()

Update all models where criterias are matched.

Parameters:

ParameterRequired?DescriptionDefault
whereYwhere conditions--
valuesYColumns to update in the model--
await this.users.update(
{ contactNumber: "XXXXXXXXXX" },
{ firstName: "New Name" }
);

The records where contactNumber is "XXXXXXXXXX" will have the updated firstName of "New Name".

exists()

Check if model exists where criterias are matched.

Parameters:

ParameterRequired?DescriptionDefault
inputsYWhere condition that is to be added--
/**
* Returns true or false
*/
const users = await this.users.exists({ contactNumber: "XXXXXXXXXX" });

count()

Get count of models matching the criterias

Parameters:

ParameterRequired?DescriptionDefault
inputsYWhere condition that is to be added--
/**
* Returns the count of models found
*/
const users = await this.users.count({ contactNumber: "XXXXXXXXXX" });

refresh()

Refresh the given model.

Parameters:

ParameterRequired?DescriptionDefault
modelYModel to be refreshed--
/**
* Return the new latest model from db
*/
let user = await this.users.firstWhere({ contactNumber: "XXXXXXXXXX" });
await this.users.update(user, { firstName: "New Name" });
user = await this.users.refresh(user);

delete()

Delete the given model.

Parameters:

ParameterRequired?DescriptionDefault
modelYModel to be refreshed--
/**
* Return the new latest model from db
*/
const user = await this.users.firstWhere({ contactNumber: "XXXXXXXXXX" });
await this.users.delete(user);

deleteWhere()

Delete models where criterias are matched.

Update all models where criterias are matched.

Parameters:

ParameterRequired?DescriptionDefault
whereYwhere conditions--
await this.users.deleteWhere({ contactNumber: "XXXXXXXXXX" });

The records where contactNumber is "XXXXXXXXXX" will be deleted.

attach()

Attach relation's ids to a model via relation.

Parameters:

ParameterRequired?DescriptionDefault
modelYmodel--
relationYrelation to be updated--
payloadYPayload to be attached--
await this.roles.attach(role, "permissions", 1);

Role model will now have Permission model with id 1 in its relation.

To know more about the payload attribute, click here.

sync()

Works like attcach only, but trashes the old pre-established relations and creates new ones.

Parameters:

ParameterRequired?DescriptionDefault
modelYmodel--
relationYrelation to be updated--
payloadYPayload to be attached--
await this.roles.sync(role, "permissions", 1);

Role model will now have only Permission model with id 1 in its relation.

To know more about the payload attribute, click here.

chunk()

Fetch models in chunks from a large table, and perform passed cb function. There can be cases where your db will contain large number of datasets, and you may want to perform action on them. Loading them all at once will be inefficient memory and time wise.

Parameters:

ParameterRequired?DescriptionDefault
whereYwhere conditions--
sizeYChunk size to be loaded from db--
cbYCallback function to be called, after each successful chunk load--
await this.users.sync({ isActive: true }, 500, (users) => console.log(users));

query()

Return new QueryBuilder instance.

/**
* Return query builder instance
*/
const query = this.query();

Transactions

If you wish to use the repository to perform some transactions, you can do so like following. This way, you get all your repository methods.

const userTrx = await this.repo.startTrx();
const users = await userTrx
.forUpdate()
.firstWhere({ id: 1 });
await userTrx.commitTrx();
// commits all queries in user repositories
// alternatively, you can also call trxRepo.rollback();

You will often run into situations where you will need to use multiple repositories during a single transaction. You can easily do it by just passing your trxRepo instance to bindTrx of another repo and you should be good to go.

const userTrx = await this.repo.startTrx();
const users = await userTrx
.forUpdate()
.firstWhere({ id: 1 });

const roleTrx = this.roleRepo.bindTrx(userTrx.getTrx());
const roles = await roleTrx.forUpdate().all();

await userTrx.commitTrx(); // commits all queries in user and role repositories

Connection Methods

If you wish to change connection of your repository on the fly, be it due to read-only connection or multi-tenancy database architecture.

bindCon()

To change, you can change the connection of your repository using bindCon method.

Parameters:

ParameterRequired?DescriptionDefault
conNameYConnection name, similar to any one of the connection name provided during configuration--

Example:

const repo = new UserRepository();
const records = await repo.bindCon("postgres-read").all();