Skip to main content

Relations

Relations work a little differently in typegoose when compared to other relational ORMs such as sequelize or typeorm. You can read more about relations (references) in typegoose [here](https://mongoosejs.com/docs/populate .html)

note

There are multiple ways to set of references in Typegoose. These are intended as a starting point.

warning

Filtering on references is not supported by Typegoose.

One to Many/Many To One Example

To set up a one to many/many to one relationship in Typegoose, you will store a reference in your document.

For example, lets add sub tasks to our todo items by storing a todoItem ref on our subTask and an array of sub-tasks on our todoItem entity.

todo-item/todo-item.entity.ts
import { Prop, modelOptions } from '@typegoose/typegoose';
import { Base } from '@typegoose/typegoose/lib/defaultClasses';

@modelOptions({ schemaOptions: { timestamps: true } })
export class TodoItemEntity extends Base {
@Prop({ required: true })
title!: string;

@Prop()
description?: string;

@Prop({ required: true })
completed!: boolean;

@Prop({ default: Date.now })
createdAt!: Date;

@Prop({ default: Date.now })
updatedAt!: Date;

@Prop({ default: 0 })
priority!: number;

@Prop({ ref: () => SubTaskEntity })
subTasks: Ref<SubTaskEntity>[];

@Prop()
createdBy?: string;

@Prop()
updatedBy?: string;
}

Now that we have the relationships defined, we can add the @Relation and @Connection to our DTOs

todo-item/todo-item.dto.ts
import { FilterableField, IDField, KeySet, Connection } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
import { SubTaskDTO } from '../../sub-task/dto/sub-task.dto';

@ObjectType('TodoItem')
@KeySet(['id'])
// disable the remove because mongoose does not support removing a virtual
@Connection('subTasks', () => SubTaskDTO, { update: { enabled: true } })
export class TodoItemDTO {
@IDField(() => ID)
id!: string;

@FilterableField()
title!: string;

@FilterableField({ nullable: true })
description?: string;

@FilterableField()
completed!: boolean;

@FilterableField(() => GraphQLISODateTime)
createdAt!: Date;

@FilterableField(() => GraphQLISODateTime)
updatedAt!: Date;

@Field()
age!: number;

@FilterableField()
priority!: number;

@FilterableField({ nullable: true })
createdBy?: string;

@FilterableField({ nullable: true })
updatedBy?: string;
}

Many To Many Example

In this example, we'll add tags to todoItems by storing an array of tag references on the todoItems.

todo-item/todo-item.entity.ts
import { Prop, modelOption, Ref } from '@typegoose/typegoose';
import { Base } from '@typegoose/typegoose/lib/defaultClasses';

@modelOptions({ schemaOptions: { timestamps: true } })
export class TodoItemEntity extends Base {
@Prop({ required: true })
title!: string;

@Prop()
description?: string;

@Prop({ required: true })
completed!: boolean;

@Prop({ default: Date.now })
createdAt!: Date;

@Prop({ default: Date.now })
updatedAt!: Date;

@Prop({ ref: () => TagEntity })
tags!: Ref<TagEntity>[];

@Prop({ default: 0 })
priority!: number;

@Prop()
createdBy?: string;

@Prop()
updatedBy?: string;

public get id(): string {
// eslint-disable-next-line no-underscore-dangle
return this._id.toHexString();
}
}

Now that we have the relationship defined, we can add the @Connection to our DTOS

todo-item/todo-item.dto.ts
import { FilterableField, IDField, KeySet, Connection } from '@ptc-org/nestjs-query-graphql';
import { ObjectType, ID, GraphQLISODateTime, Field } from '@nestjs/graphql';
import { TagDTO } from '../../tag/dto/tag.dto';

@ObjectType('TodoItem')
@KeySet(['id'])
@Connection('tags', () => TagDTO)
export class TodoItemDTO {
@IDField(() => ID)
id!: string;

@FilterableField()
title!: string;

@FilterableField({ nullable: true })
description?: string;

@FilterableField()
completed!: boolean;

@FilterableField(() => GraphQLISODateTime)
createdAt!: Date;

@FilterableField(() => GraphQLISODateTime)
updatedAt!: Date;

@Field()
age!: number;

@FilterableField()
priority!: number;

@FilterableField({ nullable: true })
createdBy?: string;

@FilterableField({ nullable: true })
updatedBy?: string;
}

Discriminators

Typegoose supports mongoose discriminators. nestjs-query provides support for them through the NestjsQueryTypegooseModule.

To use discriminators you need to define them in your NestjsQueryTypegooseModule.forFeature call.

When working with discriminators, nestjs-query requires you to provide a service class for each discriminated entity. This is necessary for the auto-generated resolvers to be created correctly. These service classes are also the perfect place to add any custom business logic for your discriminated entities.

todo-item/todo-item.module.ts
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypegooseModule, TypegooseQueryService } from '@ptc-org/nestjs-query-typegoose';
import { Module } from '@nestjs/common';
import { TodoItemEntity } from './entities/todo-item.entity';
import { TodoTaskEntity } from './entities/todo-task.entity';
import { TodoAppointmentEntity } from './entities/todo-appointment.entity';
import { TodoItemDTO } from './dto/todo-item.dto';
import { TodoTaskDTO } from './dto/todo-task.dto';
import { TodoAppointmentDTO } from './dto/todo-appointment.dto';
import { CreateTodoTaskInput } from './dto/create-todo-task.input';
import { CreateTodoAppointmentInput } from './dto/create-todo-appointment.input';
import { InjectModel } from '@m8a/nestjs-typegoose';
import { ReturnModelType } from '@typegoose/typegoose';

const typegooseModule = NestjsQueryTypegooseModule.forFeature([
{
typegooseClass: TodoItemEntity,
discriminators: [
{ typegooseClass: TodoTaskEntity },
{ typegooseClass: TodoAppointmentEntity }
]
}
]);


class TodoTaskEntityService extends TypegooseQueryService<TodoTaskEntity> {
constructor(@InjectModel(TodoTaskEntity) readonly model: ReturnModelType<typeof TodoTaskEntity>) {
super(model)
}
}

class TodoAppointmentService extends TypegooseQueryService<TodoAppointmentEntity> {
constructor(@InjectModel(TodoAppointmentEntity) readonly model: ReturnModelType<typeof TodoAppointmentEntity>) {
super(model)
}
}

@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [typegooseModule],
resolvers: [
{
DTOClass: TodoItemDTO,
EntityClass: TodoItemEntity,
create: {disabled: true}, // Disable create for the base entity
update: {disabled: true} // Disable update for the base entity
},
{
DTOClass: TodoTaskDTO,
EntityClass: TodoTaskEntity,
// Tell the resolver to use our custom input for the create operation
CreateDTOClass: CreateTodoTaskInput,
ServiceClass: TodoTaskEntityService
},
{
DTOClass: TodoAppointmentDTO,
EntityClass: TodoAppointmentEntity,
// Tell the resolver to use our custom input for the create operation
CreateDTOClass: CreateTodoAppointmentInput,
ServiceClass: TodoAppointmentService
}
],
services: [
TodoTaskEntityService,
TodoAppointmentService
]
})
]
})
export class TodoItemModule {}