Node.js and sequelize-typescript - data access objects and business objects - node.js

I am using the sequelize-typescript in my Node.js service
I have the Category class which maps to category table
import { Model, Table, Column } from "sequelize-typescript";
#Table
export class Category extends Model<Category>{
#Column
name: string
}
I also have CategoryController and CategoryService
export class CategoryController {
...
async getAll(request: Request, response: Response) {
let categories = await this.categoryService.getCatergories();
response.json(categories)
}
}
export class CategoryService {
async getCatergories(): Promise<Category[]> {
let categories = await Category.findAll<Category>()
return categories
}
}
And everything is as it should be.
But returning a Category to the controller allows it to use the use the inherited methods from the model class like:
export class CategoryController {
...
async getAll(request: Request, response: Response) {
let categories = await this.categoryService.getCatergories();
// Remove associated row in the database
categories[0].destroy()
response.json(categories)
}
}
I was thinking to create a CategoryModel class like this:
export class CategoryModel {
id : number
name : string
}
And modify all methods in CategoryService to return CategoryModel instances instead of Category and rename Category to CategoryEntity
What is the best way to deal with such a problem?

Use toJSON() of Category instance to get "a JSON representation" of the instance.
See sequelize docs for more information: http://docs.sequelizejs.com/class/lib/model.js~Model.html#instance-method-toJSON
Additionally you could add an interface to achieve type safety for the return value of toJSON() instead of defining another class:
interface ICategory {
id: number;
name: string;
}
#Table
export class Category extends Model<Category> implements ICategory{
#Column
name: string
}
Using toJSON():
Category.findOne(result => {
const category: ICategory = result.toJSON();
});

Related

TypeScript - Repository pattern with Sequelize

I'm converting my Express API Template to TypeScript and I'm having some issues with the repositories.
With JavaScript, I would do something like this:
export default class BaseRepository {
async all() {
return this.model.findAll();
}
// other common methods
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository {
constructor() {
super();
this.model = User;
}
async findByEmail(email) {
return this.model.findOne({
where: {
email,
},
});
}
// other methods
Now, with TypeScript, the problem is that it doesn't know the type of this.model, and I can't pass a concrete model to BaseRepository, because, well, it is an abstraction. I've found that sequelize-typescript exports a ModelCtor which declares all the static model methods like findAll, create, etc., and I also could use another sequelize-typescript export which is Model to properly annotate the return type.
So, I ended up doing this:
import { Model, ModelCtor } from 'sequelize-typescript';
export default abstract class BaseRepository {
protected model: ModelCtor;
constructor(model: ModelCtor) {
this.model = model;
}
public async all(): Promise<Model[]> {
return this.model.findAll();
}
// other common methods
}
import { Model } from 'sequelize-typescript';
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository {
constructor() {
super(User);
}
public async findByEmail(email: string): Promise<Model | null> {
return this.model.findOne({
where: {
email,
},
});
}
// other methods
}
Ok, this works, TypeScript doesn't complain about methods like findOne or create not existing, but that generates another problem.
Now, for example, whenever I get a User from the repository, if I try to access one of its properties, like user.email, TypeScript will complain that this property does not exist. Of course, because the type Model does not know about the specifics of each model.
Ok, it's treason generics then.
Now BaseRepository uses a generic Model type which the methods also use:
export default abstract class BaseRepository<Model> {
public async all(): Promise<Model[]> {
return Model.findAll();
}
// other common methods
}
And the concrete classes pass the appropriate model to the generic type:
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository<User> {
public async findByEmail(email: string): Promise<User | null> {
return User.findOne({
where: {
email,
},
});
}
// other methods
}
Now IntelliSense lights up correctly, it shows both abstract and concrete classes methods and the model properties (e.g. user.email).
But, as you have imagined, that leads to more problems.
Inside BaseRepository, where the methods use the Model generic type, TypeScript complains that 'Model' only refers to a type, but is being used as a value here. Not only that, but TypeScript also doesn't know (again) that the static methods from the model exist, like findAll, create, etc.
Another problem is that in both abstract and concrete classes, as the methods don't use this anymore, ESLint expects the methods to be static: Expected 'this' to be used by class async method 'all'. Ok, I can just ignore this rule in the whole file and the error is gone. It would be even nicer to have all the methods set to static, so I don't have to instantiate the repository, but maybe I'm dreaming too much.
Worth mentioning that although I can just silence those errors with // #ts-ignore, when I execute this, it doesn't work: TypeError: Cannot read property 'create' of undefined\n at UserRepository.<anonymous>
I researched a lot, tried to make all methods static, but static methods can't reference the generic type (because it is considered an instance property), tried some workarounds, tried to pass the concrete model in the constructor of BaseRepository along with the class using the generic type, but nothing seems to work so far.
In case you want to check the code: https://github.com/andresilva-cc/express-api-template/tree/main/src/App/Repositories
EDIT:
Found this: Sequelize-Typescript typeof model
Ok, I removed some unnecessary code from that post and that kinda works:
import { Model } from 'sequelize-typescript';
export default abstract class BaseRepository<M extends Model> {
constructor(protected model: typeof Model) {}
public async all(attributes?: string[]): Promise<M[]> {
// Type 'Model<{}, {}>[]' is not assignable to type 'M[]'.
// Type 'Model<{}, {}>' is not assignable to type 'M'.
// 'Model<{}, {}>' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Model<any, any>'.
return this.model.findAll({
attributes,
});
}
import BaseRepository from './BaseRepository';
import { User } from '../Models';
export default class UserRepository extends BaseRepository<User> {
constructor() {
super(User);
}
}
I mean, if I put some // #ts-ignore it at least executes, and IntelliSense lights up perfectly, but TypeScript complains.
We faced the same problem. The solution was to declare returning types with an interface that an abstract repository class implements.
Code for the interface:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
export interface IRepo<M> {
save(model: M): RepoResult<M>;
findById(id: string): RepoResult<M>;
search(parameterName: string, parameterValue: string, sortBy: string, order: number, pageSize: number, pageNumber: number): RepoResult<M[]>;
getAll(): RepoResult<M[]>;
deleteById(id: string): RepoResult<M>;
findByIds(ids: string[]): RepoResult<M[]>;
deleteByIds(ids: string[]): RepoResult<any>;
};
Code for the abstract class:
export abstract class Repo<M extends sequelize.Model> implements IRepo<M> {
protected Model!: sequelize.ModelCtor<M>;
constructor(Model: sequelize.ModelCtor<M>) {
this.Model = Model;
}
public async save(doc: M) {
try {
const savedDoc = await doc.save();
return Result.ok(savedDoc);
} catch (ex: any) {
logger.error(ex);
return Result.fail(new RepoError(ex.message, 500));
}
}
public async findById(id: string) {
try {
const doc = await this.Model.findOne({where: {
id: id
}});
if (!doc) {
return Result.fail(new RepoError('Not found', 404));
}
return Result.ok(doc);
} catch (ex: any) {
return Result.fail(new RepoError(ex.message, 500));
}
}
}
Hope it helps. Have a nice day:)
EDIT:
Result is a class that looks like this:
export class Result<V, E> {
public isSuccess: boolean;
public isFailure: boolean;
private error: E;
private value: V;
private constructor(isSuccess: boolean, value: V, error: E) {
if (isSuccess && error) {
throw new Error('Successful result must not contain an error');
} else if (!isSuccess && value) {
throw new Error('Unsuccessful error must not contain a value');
}
this.isSuccess = isSuccess;
this.isFailure = !isSuccess;
this.value = value;
this.error = error;
}
public static ok<V>(value: V): Result<V, undefined> {
return new Result(true, value, undefined);
}
public static fail<E>(error: E): Result<undefined, E> {
return new Result(false, undefined, error);
}
public getError(): E {
if (this.isSuccess) {
throw new Error('Successful result does not contain an error');
}
return this.error;
}
public getValue(): V {
if (this.isFailure) {
throw new Error('Unsuccessful result does not contain a value');
}
return this.value;
}
}
RepoError class:
type RepoErrorCode = 404 | 500;
export class RepoError extends Error {
public code: RepoErrorCode;
constructor(message: string, code: RepoErrorCode) {
super(message);
this.code = code;
}
}
RepoResult type:
export type RepoResult<M> = Promise<Result<M | undefined, RepoError | undefined>>;
You can find more info on the pattern at the link below:
https://khalilstemmler.com/articles/enterprise-typescript-nodejs/functional-error-handling/

Correctly Saving and Updating Entites in Netsjs+TypeORM

I've got a Question regarding TypeORM-Relations and how to use them 'nest-like'.
Suppose I have two Entities defined ChildEntity and TestEntity, which are related.
TestEntity:
import { ChildEntity } from 'src/modules/child-entity/entities/child-entity.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class TestEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string;
#ManyToOne(() => ChildEntity, (childEntity) => childEntity.testEntities)
childEntity: ChildEntity;
constructor(name: string, childEntity: ChildEntity) {
this.name = name;
this.childEntity = childEntity;
}
}
My first question occurs when I want to create the entity. I have to first translate the passed childEntityId into a ChildEntity, which I can pass to the constructor:
CreateTestEntityDto
import { ApiProperty } from '#nestjs/swagger';
import { IsNotEmpty, IsNumber } from 'class-validator';
export class CreateTestEntityDto {
#ApiProperty()
#IsNotEmpty()
name: string;
#ApiProperty()
#IsNumber()
childEntityId: number;
constructor(name: string, childEntityId: number) {
this.name = name;
this.childEntityId = childEntityId;
}
}
async create(createTestEntityDto: CreateTestEntityDto) {
const { name, childEntityId } = createTestEntityDto;
const childEntity = await this.childEntityService.findOne(childEntityId);
const testEntity = new TestEntity(name, childEntity);
return this.testEntityRepo.save(testEntity);
}
Is there a way to just pass the childEntityId to the save()-Method without explicitly looking for the ChildEntity beforehand?
The Second problem occurs when updating.
UpdateTestEntityDto
import { PartialType } from '#nestjs/swagger';
import { CreateTestEntityDto } from './create-test-entity.dto';
export class UpdateTestEntityDto extends PartialType(CreateTestEntityDto) {}
As updating only a partial Entity is possible I have to check if the Id is even passed along the request and if it is I have to retrieve the correct Entity for the update. Is there a more streamlined way to do this?
async update(id: number, updateTestEntityDto: UpdateTestEntityDto) {
const { name, childEntityId } = updateTestEntityDto;
const props = { name };
if (childEntityId) {
props['childEntity'] = await this.childEntityService.findOne(
childEntityId,
);
}
return this.testEntityRepo.update(id, props);
}
You should add a childEntityId to the test entity:
#Entity()
export class TestEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string;
#Column('int')
childEntityId: number;
#ManyToOne(() => ChildEntity, (childEntity) => childEntity.testEntities)
childEntity: ChildEntity;
...
}
and then you can use it to set the id directly. Something like:
async create(dto: Dto) {
const { name, childEntityId } = dto;
const entity = new TestEntity();
entity.name = name;
entity.childEntityId = childEntityId;
return this.testEntityRepo.save(entity);
}
Check this out.
1.) Saving relational entity
There's no need to do all these roundtrips cluttering to save the entity. While, the solution given by #UrosAndelic works but still there's no need to write 3 extra lines of code.
If you hover over a relational param inside the create() method of the repository from an IDE, you'll notice that it accepts two types. First, An Instance of an entity OR Second, a DeepPartial object of an entity.
For instance:
const entity = this.testEntityRepo.create({
name: 'Example 1',
childEntity: {
id: childEntityId // notice: it's a DeepPartial object of ChildEntity
}
})
await this.testEntityRepo.save(entity)
2.) Updating entity
There's no need for child entity's id if you are updating test entity. You can simply update the props of test entity.
const testEntityId = 1;
await this.testEntityRepo.update(testEntityId, {
name: 'Example 2'
})
This will update the name of TestEntity = 1;

How to set table name in #Entity dynamically?

I read the article https://medium.com/#terence410/working-with-dynamic-table-name-with-typeorm-6a67128b9671
import {Entity, PrimaryColumn, Column} from "typeorm";
export function createEntity(tableName: string) {
#Entity({name: tableName})
class EntityClass {
public static tableName = tableName;
#PrimaryColumn()
public name: string = "";
#Column()
public value: number = 0;
}
return EntityClass;
}
I have error - Return type of exported function has or is using private name 'EntityClass'.
How to set table name dynamic ?
I also followed the post the article https://medium.com/#terence410/working-with-dynamic-table-name-with-typeorm-6a67128b9671. It need to do some change in code to make it work.
Here is the code:
[1] Create the base entity, this entity will contain all properties that you need for your model and we can export this entity for other the class use.
import {Column, PrimaryGeneratedColumn} from 'typeorm'
export class BaseCategory {
#PrimaryGeneratedColumn({unsigned: true})
id: number
#Column({type: 'varchar', length: 255, nullable: true})
name: string
#Column({type: 'bigint', width: 3, nullable: true})
quantity: number
}
[2] Define the CategoryService
#Injectable()
export class CategoryService {
constructor(
#Logger() private readonly logger: LoggerService,
private configService: ConfigService
) {}
public connections: Map<any, Promise<Connection>> = new Map()
/**
* Gets the connection for dynamic entity. By this way, the dynamic entity can register with
* EntityManger and can get the repository for this dynamic entity.
* If don't use this way, the EntityManager don't know the dynamic entity and will throw error
* when try to get the repository for this dynamic entity.
*
* There is the problem with approach: there is too many connections, each merchant will help its own connection.
*
* #param entityType
* #returns
*/
public async getConnection(entityType: object) {
const commissionTableName = (entityType as any).tableName
if (!this.connections.has(commissionTableName)) {
const mysqlConfig = this.configService.get('mysql')
const name = `table:${commissionTableName}`
const newOptions = {...mysqlConfig, name, entities: [entityType] as any}
const connection = createConnection(newOptions)
this.connections.set(commissionTableName, connection)
}
return this.connections.get(commissionTableName) as Promise<Connection>
}
public async getRepository(
dynamicTableName
): Promise<Repository<BaseCategory>> {
#Entity({name: dynamicTableName})
class EntityClass extends BaseCategory {
public static tableName = dynamicTableName
}
const connection = await this.getConnection(EntityClass)
const repository = connection.getRepository(
dynamicTableName
) as Repository<BaseCategory>
return repository
}
public async create() {
// Example we want to create a new entity & table `organ_category`
const repository = await this.getRepository('organ_category')
const entity = new BaseCategory()
await repository.save(entity)
}
}
It worked for me. There are some consider points with this code:
Each category will one connection, it is not good point but we can improve it by using list of dynamic table to init instead of
init one by one. By this way, it can reduce number of connection.

NestJs Dependency injection get multiple instance in a single class

I have a class Item below
#Injectable()
export class Item {
name: string;
}
I am injecting Item class into ItemService
export class ItemService {
constructor(private readonly item: Item) {}
save(items) {
items.forEach((data) => {
this.item.name = data.name;
this.item.save();
});
}
}
The problem here is I am not getting new instance of item for every loop. How can I achieve this with dependency injection in nestjs.
I am not getting new instance of item for every loop.
Blockquote
That's what dependency injection is, you inject the already created object of class if available, It follows the singleton pattern.
How can I achieve this with dependency injection in nestjs.
By creating a new object in each iteration;
import item from 'Item'
export class ItemService {
constructor() {}
save(items) {
items.forEach((data) => {
item = new Item()
item.name = data.name;
item.save();
});
}
}

How to create two subclasses of the model class in sequelize-typescript?

I'm creating a node.js server with sequelize using sequelize-typescript. I have one entity - name it Employee.
In database all profiles stores in the same table but have two different types - say Manager and Operator.
Each type has its own relations with different tables - say:
Manager -> ManagerOptions
Operator -> OperatorOptions
I've created a model Employee and two subclasses - Manager and Operator. Each extending Employee, so have same base properties, and also have some additional properties in each subclass, such as ManagerOprtions and OperatorOptions.
// Employee.ts
import { Table, Column, Model } from 'sequelize-typescript';
#Table({
tableName: 'employees'
})
export class Employee extends Model<Employee> {
#Column public type_id: number;
#Column public name: string;
#Column public url: string;
}
// Manager.ts
import { Column, HasOne } from 'sequelize-typescript';
import { Employee } from './Employee';
import { ManagerInfo } from './ManagerInfo';
export class Manager extends Employee {
#Column public subordinate_count: number;
#HasOne(() => ManagerInfo, {
foreignKey: 'manager_id',
as: 'info'
})
public info: ManagerInfo;
}
// Operator.ts
import { Column, HasOne } from 'sequelize-typescript';
import { Employee } from './Employee';
import { OperatorInfo } from './OperatorInfo';
export class Operator extends Employee {
#Column public calls_count: number;
#HasOne(() => OperatorInfo, {
foreignKey: 'operator_id',
as: 'info'
})
public info: OperatorInfo;
}
How do I create such relations using sequelize-typescript (or just sequelize, if you're not familiar with this lib), so I can search Employees by name and have different models in the result set?
Search: "Mar"
Results:
+---------------------------------------+
| 1 Mark Operator OperatorOptions |
| 2 Mary Manager ManagerOptions |
+---------------------------------------+
Hope I've explained it right.

Resources