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.
Each model need to have its two files, One Contract and One Database Repository Implementation
To create UserRepositoryContract interface, simply do
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.
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
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.
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
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
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:
Parameter | Required? | Description | Default |
---|---|---|---|
inputs | Y | Where condition that is to be added | undefined |
error | N | Throw exception if model is not found | true |
/**
* 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:
Parameter | Required? | Description | Default |
---|---|---|---|
inputs | Y | Where condition that is to be added | -- |
error | N | Throw exception if model is not found | true |
/**
* 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:
Parameter | Required? | Description | Default |
---|---|---|---|
inputs | Y | Column 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:
Parameter | Required? | Description | Default |
---|---|---|---|
condition | Y | Existence of any model is checked using the object | -- |
values | Y | If 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:
Parameter | Required? | Description | Default |
---|---|---|---|
condition | Y | Existence of any model is checked using the object | -- |
values | Y | If 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:
Parameter | Required? | Description | Default |
---|---|---|---|
model | Y | To update model | -- |
values | Y | Columns 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:
Parameter | Required? | Description | Default |
---|---|---|---|
where | Y | where conditions | -- |
values | Y | Columns 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:
Parameter | Required? | Description | Default |
---|---|---|---|
inputs | Y | Where 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:
Parameter | Required? | Description | Default |
---|---|---|---|
inputs | Y | Where 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:
Parameter | Required? | Description | Default |
---|---|---|---|
model | Y | Model 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:
Parameter | Required? | Description | Default |
---|---|---|---|
model | Y | Model 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:
Parameter | Required? | Description | Default |
---|---|---|---|
where | Y | where 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:
Parameter | Required? | Description | Default |
---|---|---|---|
model | Y | model | -- |
relation | Y | relation to be updated | -- |
payload | Y | Payload 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:
Parameter | Required? | Description | Default |
---|---|---|---|
model | Y | model | -- |
relation | Y | relation to be updated | -- |
payload | Y | Payload 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:
Parameter | Required? | Description | Default |
---|---|---|---|
where | Y | where conditions | -- |
size | Y | Chunk size to be loaded from db | -- |
cb | Y | Callback 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:
Parameter | Required? | Description | Default |
---|---|---|---|
conName | Y | Connection 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();