DEV Community

Cover image for Three in one (code first) : NestJs & GraphQl & Mongoose
Lotfi
Lotfi

Posted on • Updated on • Originally published at lotfimeziani.dev

Three in one (code first) : NestJs & GraphQl & Mongoose

Follow me on Twitter, happy to take your suggestions and improvements.

I decided to write this blog post after working on a personal project. As I started it, I thought about a way to put in place a solid architecture to create a GraphQL API with NestJs and Mongoose.

Why GraphQL?

  • No more Over- and Underfetching ;
  • Rapid Product Iterations on the Frontend ;
  • Insightful Analytics on the Backend ;
  • Benefits of a Schema & Type System ;

-- How to GraphQL

.

Why NestJs?

  • Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify) but also exposes their APIs directly to the developer. This allows developers the freedom to use the myriad of third-party modules that are available for the underlying platform.

-- Filipe Mazzon

.

Why Mongoose?

  • MongooseJS provides an abstraction layer on top of MongoDB that eliminates the need to use named collections.
  • Models in Mongoose perform the bulk of the work of establishing up default values for document properties and validating data.
  • Functions may be attached to Models in MongooseJS. This allows for seamless incorporation of new functionality.
  • Queries use function chaining rather than embedded mnemonics which result in code that is more flexible and readable, therefore more maintainable as well.

-- Jim Medlock


Problem

There is a question that always comes up when I start to set up the architecture of a project, it is the definition of the data model and how the different layers of the application will consume it. In my case, the definition of a data model for the different layers of the application gives me some irritation 😓:

  • The definition of a schema for GraphQL to implement the API endpoint ;
  • The definition of a schema for Mongoose to organize the documents of the database ;
  • The definition of a data model so that the application map objects ;

Solution

The ideal is to have to define the data model only once and thus it will be used to generate the GraphQL schema, the MongoDB collection schemas as well as the classes used by NestJs providers. And the magic is that NestJs with its plugins allow us to do it easily 😙.

NestJs plugins

NestJS plugins encapsulate different technologies in NestJs modules for easy use and integration into the NestJs ecosystem. In this case, we will use the following two plugins: @nestjs/mongoose and @nestjs/graphql

These two plugins allow us to proceed in two ways:

  • schema-first: first, define the schemas for Mongoose and for GraphQL, then use it to generate our typescript classes.
  • code-first: first, define our typescript classes, then use them to generate our schemas Mongoose/GraphQL.

I used the code-first approach because it allows me to implement a single model (typescript classes) and use it to generate my schemas for GraphQL as well as for Mongoose 👌.


Implementation

Here is the final project source code on GitHub.

Okay, I've talked too much. Warm-up our fingers to do some magic 🧙!

NestJS

First, create our NestJs project using @nestjs/cli. We will call it three-in-one-project:

$ npm i -g @nestjs/cli
$ nest new three-in-one-project
Enter fullscreen mode Exit fullscreen mode

This will initiate our NestJs project:

Alt Text

What interests us here is the content of the src/ folder :

  • main.ts: the entry point of the NestJS app where we bootstrap it.
  • app.module.ts: the root module of the NestJS app. It implemente a controller AppController and a provider AppService.

To serve the nest server run :

$ npm start
Enter fullscreen mode Exit fullscreen mode

Alt Text

For better organization, we will put the AppModule files in a dedicated folder src/app/ and update the import path of AppModule in main.ts:

Alt Text

Don't forget to update the import path of AppModule in main.ts

Model

We are going to create an API that manages a list of Peron who have a list of Hobby for that we will create these two model in app/ folder:

// person.model.ts

export class Person {
  _id: string;
  name: string;
  hobbies: Hobby[];
}
Enter fullscreen mode Exit fullscreen mode
// hobby.model.ts

export class Hobby {
  _id: string;
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Mongoose

Installing dependencies

From the two classes Hobby and Person we will generate the corresponding mongoose schema HobbySchema and PersonSchema. For that we will install the package @nestjs/mongoose with some other dependencies :

$ npm i @nestjs/mongoose mongoose
$ npm i --save-dev @types/mongoose
Enter fullscreen mode Exit fullscreen mode

Connection to MongoDB

To connecte our backend to mongoDB data base, we will import in AppModule the MongooseModule :

// app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  // "mongodb://localhost:27017/three-in-one-db" is the connection string to the project db
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/three-in-one-db'),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

For better organization we will create two sub-modules PersonModule and HobbyModule each manage the corresponding model. We will use @nestjs/cli to do that quickly :

$ cd src\app\
$ nest generate module person
$ nest generate module hobby
Enter fullscreen mode Exit fullscreen mode

The created modules PersonModule and HobbyModule are automatically imported in AppModule.

Now, move each file person.model.ts and hobby.model.ts into its corresponding modules :

Alt Text

Schema generation

We can now start to set up the generation of mongoose schemas. @nestjs/mongoose gives us a decorators to annotate our typescript classes to indicate how to generate the mongoose schemas. let's add some decorator to our classes:

// person.model.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema as MongooseSchema } from 'mongoose';

import { Hobby } from '../hobby/hobby.model';

@Schema()
export class Person {
  _id: MongooseSchema.Types.ObjectId;

  @Prop()
  name: string;

  @Prop()
  hobbies: Hobby[];
}

export type PersonDocument = Person & Document;

export const PersonSchema = SchemaFactory.createForClass(Person);
Enter fullscreen mode Exit fullscreen mode
// hobby.model.ts

import { Document, Schema as MongooseSchema } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

@Schema()
export class Hobby {
  _id: MongooseSchema.Types.ObjectId;

  @Prop()
  name: string;
}

export type HobbyDocument = Hobby & Document;

export const HobbySchema = SchemaFactory.createForClass(Hobby);
Enter fullscreen mode Exit fullscreen mode
  • The @Prop() decorator defines a property in the document.
  • The @Schema() decorator marks a class as a schema definition
  • Mongoose documents (ex: PersonDocument) represent a one-to-one mapping to documents as stored in MongoDB.
  • MongooseSchema.Types.ObjectId is a mongoose type typically used for unique identifiers

And finally, we import the model to MongooseModule in our two modules :

// person.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

import { Person, PersonSchema } from './person.model';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: Person.name, schema: PersonSchema }]),
  ],
})
export class PersonModule {}
Enter fullscreen mode Exit fullscreen mode
// hobby.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

import { Hobby, HobbySchema } from './hobby.model';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: Hobby.name, schema: HobbySchema }]),
  ],
})
export class HobbyModule {}
Enter fullscreen mode Exit fullscreen mode

CRUD Operations

We will create the layer for our two modules witch implement CRUD operations. For each module, create a NestJS service who implements that. To create the two services, execute the following commands :

$ cd src\app\person\
$ nest generate service person --flat
$ cd ..\hobby\
$ nest generate service hobby --flat
Enter fullscreen mode Exit fullscreen mode

we use --flat to not generate a folder for the service.

Alt Text

Update services to add CRUD methods and their inputs classes :

// person.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Schema as MongooseSchema } from 'mongoose';

import { Person, PersonDocument } from './person.model';
import {
  CreatePersonInput,
  ListPersonInput,
  UpdatePersonInput,
} from './person.inputs';

@Injectable()
export class PersonService {
  constructor(
    @InjectModel(Person.name) private personModel: Model<PersonDocument>,
  ) {}

  create(payload: CreatePersonInput) {
    const createdPerson = new this.personModel(payload);
    return createdPerson.save();
  }

  getById(_id: MongooseSchema.Types.ObjectId) {
    return this.personModel.findById(_id).exec();
  }

  list(filters: ListPersonInput) {
    return this.personModel.find({ ...filters }).exec();
  }

  update(payload: UpdatePersonInput) {
    return this.personModel
      .findByIdAndUpdate(payload._id, payload, { new: true })
      .exec();
  }

  delete(_id: MongooseSchema.Types.ObjectId) {
    return this.personModel.findByIdAndDelete(_id).exec();
  }
}
Enter fullscreen mode Exit fullscreen mode
// person.inputs.ts

import { Schema as MongooseSchema } from 'mongoose';
import { Hobby } from '../hobby/hobby.model';

export class CreatePersonInput {
  name: string;
  hobbies: Hobby[];
}

export class ListPersonInput {
  _id?: MongooseSchema.Types.ObjectId;
  name?: string;
  hobbies?: Hobby[];
}

export class UpdatePersonInput {
  _id: MongooseSchema.Types.ObjectId;
  name?: string;
  hobbies?: Hobby[];
}
Enter fullscreen mode Exit fullscreen mode
// hobby.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Schema as MongooseSchema } from 'mongoose';

import { Hobby, HobbyDocument } from './hobby.model';
import {
  CreateHobbyInput,
  ListHobbyInput,
  UpdateHobbyInput,
} from './hobby.inputs';

@Injectable()
export class HobbyService {
  constructor(
    @InjectModel(Hobby.name) private hobbyModel: Model<HobbyDocument>,
  ) {}

  create(payload: CreateHobbyInput) {
    const createdHobby = new this.hobbyModel(payload);
    return createdHobby.save();
  }

  getById(_id: MongooseSchema.Types.ObjectId) {
    return this.hobbyModel.findById(_id).exec();
  }

  list(filters: ListHobbyInput) {
    return this.hobbyModel.find({ ...filters }).exec();
  }

  update(payload: UpdateHobbyInput) {
    return this.hobbyModel
      .findByIdAndUpdate(payload._id, payload, { new: true })
      .exec();
  }

  delete(_id: MongooseSchema.Types.ObjectId) {
    return this.hobbyModel.findByIdAndDelete(_id).exec();
  }
}
Enter fullscreen mode Exit fullscreen mode
// hobby.inputs.ts

export class CreateHobbyInput {
  name: string;
}

export class ListHobbyInput {
  _id?: MongooseSchema.Types.ObjectId;
  name?: string;
}

export class UpdateHobbyInput {
  _id: MongooseSchema.Types.ObjectId;
  name?: string;
}
Enter fullscreen mode Exit fullscreen mode

Note that the attribute hobbies of Hobby class is an array of reference to Hobby object. So let's make some adjustment:

// person.model.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema as MongooseSchema } from 'mongoose';

import { Hobby } from '../hobby/hobby.model';

@Schema()
export class Person {
  _id: MongooseSchema.Types.ObjectId;

  @Prop()
  name: string;

  @Prop({ type: [MongooseSchema.Types.ObjectId], ref: Hobby.name })
  hobbies: MongooseSchema.Types.ObjectId[];
}

export type PersonDocument = Person & Document;

export const PersonSchema = SchemaFactory.createForClass(Person);
Enter fullscreen mode Exit fullscreen mode
// person.inputs.ts

import { Hobby } from '../hobby/hobby.model';
import { Schema as MongooseSchema } from 'mongoose';

export class CreatePersonInput {
  name: string;
  hobbies: MongooseSchema.Types.ObjectId[];
}

export class ListPersonInput {
  _id?: MongooseSchema.Types.ObjectId;
  name?: string;
  hobbies?: MongooseSchema.Types.ObjectId[];
}

export class UpdatePersonInput {
  _id: MongooseSchema.Types.ObjectId;
  name?: string;
  hobbies?: MongooseSchema.Types.ObjectId[];
}
Enter fullscreen mode Exit fullscreen mode

GraphQL

We are almost done, we just have to implement the graphQL layer.

Dependencies installation

$ npm i @nestjs/graphql graphql-tools graphql apollo-server-express
Enter fullscreen mode Exit fullscreen mode

Schema generation

As for the mongoose, we will use decorators from @nestjs/graphql to annotate our typescript classes to indicate how to generate the graphQL schemas:

// person.model.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Field, ObjectType } from '@nestjs/graphql';
import { Document, Schema as MongooseSchema } from 'mongoose';

import { Hobby } from '../hobby/hobby.model';

@ObjectType()
@Schema()
export class Person {
  @Field(() => String)
  _id: MongooseSchema.Types.ObjectId;

  @Field(() => String)
  @Prop()
  name: string;

  @Field(() => [String])
  @Prop({ type: [MongooseSchema.Types.ObjectId], ref: Hobby.name })
  hobbies: MongooseSchema.Types.ObjectId[];
}

export type PersonDocument = Person & Document;

export const PersonSchema = SchemaFactory.createForClass(Person);
Enter fullscreen mode Exit fullscreen mode
// hobby.model.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Field, ObjectType } from '@nestjs/graphql';
import { Document, Schema as MongooseSchema } from 'mongoose';

@ObjectType()
@Schema()
export class Hobby {
  @Field(() => String)
  _id: MongooseSchema.Types.ObjectId;

  @Field(() => String)
  @Prop()
  name: string;
}

export type HobbyDocument = Hobby & Document;

export const HobbySchema = SchemaFactory.createForClass(Hobby);
Enter fullscreen mode Exit fullscreen mode

For more information about @nestjs/graphql decorators ObjectType and Field: official doc

Resolvers

To define our graphQL query, mutation, and resolvers we will create a NestJS resolver. we can use the NestJs CLI to do that:

$ cd src\app\person\
$ nest generate resolver person --flat
$ cd ..\hobby\
$ nest generate resolver hobby --flat
Enter fullscreen mode Exit fullscreen mode

Then, update the generated files :

// person.resolver.ts

import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Schema as MongooseSchema } from 'mongoose';

import { Person } from './person.model';
import { PersonService } from './person.service';
import {
  CreatePersonInput,
  ListPersonInput,
  UpdatePersonInput,
} from './person.inputs';

@Resolver(() => Person)
export class PersonResolver {
  constructor(private personService: PersonService) {}

  @Query(() => Person)
  async person(
    @Args('_id', { type: () => String }) _id: MongooseSchema.Types.ObjectId,
  ) {
    return this.personService.getById(_id);
  }

  @Query(() => [Person])
  async persons(
    @Args('filters', { nullable: true }) filters?: ListPersonInput,
  ) {
    return this.personService.list(filters);
  }

  @Mutation(() => Person)
  async createPerson(@Args('payload') payload: CreatePersonInput) {
    return this.personService.create(payload);
  }

  @Mutation(() => Person)
  async updatePerson(@Args('payload') payload: UpdatePersonInput) {
    return this.personService.update(payload);
  }

  @Mutation(() => Person)
  async deletePerson(
    @Args('_id', { type: () => String }) _id: MongooseSchema.Types.ObjectId,
  ) {
    return this.personService.delete(_id);
  }
}
Enter fullscreen mode Exit fullscreen mode
// hobby.resolver.ts

import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Schema as MongooseSchema } from 'mongoose';

import { Hobby } from './hobby.model';
import { HobbyService } from './hobby.service';
import {
  CreateHobbyInput,
  ListHobbyInput,
  UpdateHobbyInput,
} from './hobby.inputs';

@Resolver(() => Hobby)
export class HobbyResolver {
  constructor(private hobbyService: HobbyService) {}

  @Query(() => Hobby)
  async hobby(
    @Args('_id', { type: () => String }) _id: MongooseSchema.Types.ObjectId,
  ) {
    return this.hobbyService.getById(_id);
  }

  @Query(() => [Hobby])
  async hobbies(@Args('filters', { nullable: true }) filters?: ListHobbyInput) {
    return this.hobbyService.list(filters);
  }

  @Mutation(() => Hobby)
  async createHobby(@Args('payload') payload: CreateHobbyInput) {
    return this.hobbyService.create(payload);
  }

  @Mutation(() => Hobby)
  async updateHobby(@Args('payload') payload: UpdateHobbyInput) {
    return this.hobbyService.update(payload);
  }

  @Mutation(() => Hobby)
  async deleteHobby(
    @Args('_id', { type: () => String }) _id: MongooseSchema.Types.ObjectId,
  ) {
    return this.hobbyService.delete(_id);
  }
}
Enter fullscreen mode Exit fullscreen mode

For more information about @nestjs/graphql decorators Mutation, Resolver,and Query: official doc

let's add some decorators to inputs classes so that GraphQl recognizes them :

// person.inputs.ts

import { Field, InputType } from '@nestjs/graphql';
import { Schema as MongooseSchema } from 'mongoose';

import { Hobby } from '../hobby/hobby.model';

@InputType()
export class CreatePersonInput {
  @Field(() => String)
  name: string;

  @Field(() => [String])
  hobbies: MongooseSchema.Types.ObjectId[];
}

@InputType()
export class ListPersonInput {
  @Field(() => String, { nullable: true })
  _id?: MongooseSchema.Types.ObjectId;

  @Field(() => String, { nullable: true })
  name?: string;

  @Field(() => [String], { nullable: true })
  hobbies?: MongooseSchema.Types.ObjectId[];
}

@InputType()
export class UpdatePersonInput {
  @Field(() => String)
  _id: MongooseSchema.Types.ObjectId;

  @Field(() => String, { nullable: true })
  name?: string;

  @Field(() => [String], { nullable: true })
  hobbies?: MongooseSchema.Types.ObjectId[];
}
Enter fullscreen mode Exit fullscreen mode
// hobby.inputs.ts

import { Schema as MongooseSchema } from 'mongoose';
import { Field, InputType } from '@nestjs/graphql';

@InputType()
export class CreateHobbyInput {
  @Field(() => String)
  name: string;
}

@InputType()
export class ListHobbyInput {
  @Field(() => String, { nullable: true })
  _id?: MongooseSchema.Types.ObjectId;

  @Field(() => String, { nullable: true })
  name?: string;
}

@InputType()
export class UpdateHobbyInput {
  @Field(() => String)
  _id: MongooseSchema.Types.ObjectId;

  @Field(() => String, { nullable: true })
  name?: string;
}
Enter fullscreen mode Exit fullscreen mode

Import GraphQLModule

Finally, import the GraphQLModule in AppModule:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

import { PersonModule } from './person/person.module';
import { HobbyModule } from './hobby/hobby.module';

import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/three-in-one-db'),
    GraphQLModule.forRoot({
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      sortSchema: true,
      playground: true,
      debug: false,
    }),
    PersonModule,
    HobbyModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode
  • autoSchemaFile property value is the path where your automatically generated schema will be created
  • sortSchema the types in the generated schema will be in the order they are defined in the included modules. To sort the schema lexicographically turn this attribute to true
  • playground to activate graqh-playground
  • debug to turn on/off debug mode

GraphQL playground

The playground is a graphical, interactive, in-browser GraphQL IDE, available by default on the same URL as the GraphQL server itself. To access the playground, you need a basic GraphQL server configured and running.
-- NestJs Doc

We have already activated playground in the previous step, once the server is started (yarn start), we can access it via the following URL: http://localhost:3000/graphql :

Alt Text

Populate & ResolveField

Mongoose has a powerful method called populate(), which lets you reference documents in other collections.

Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, a plain object, multiple plain objects, or all objects returned from a query. Let's look at some examples.
-- Mongoose Doc

We can use Mongoose populate to resolve hobbies field of Person :

// person.resolver.ts

import {
  Args,
  Mutation,
  Parent,
  Query,
  ResolveField,
  Resolver,
} from '@nestjs/graphql';
import { Schema as MongooseSchema } from 'mongoose';

import { Person, PersonDocument } from './person.model';
import { PersonService } from './person.service';
import {
  CreatePersonInput,
  ListPersonInput,
  UpdatePersonInput,
} from './person.inputs';
import { Hobby } from '../hobby/hobby.model';

@Resolver(() => Person)
export class PersonResolver {
  constructor(private personService: PersonService) {}

  @Query(() => Person)
  async person(
    @Args('_id', { type: () => String }) _id: MongooseSchema.Types.ObjectId,
  ) {
    return this.personService.getById(_id);
  }

  @Query(() => [Person])
  async persons(
    @Args('filters', { nullable: true }) filters?: ListPersonInput,
  ) {
    return this.personService.list(filters);
  }

  @Mutation(() => Person)
  async createPerson(@Args('payload') payload: CreatePersonInput) {
    return this.personService.create(payload);
  }

  @Mutation(() => Person)
  async updatePerson(@Args('payload') payload: UpdatePersonInput) {
    return this.personService.update(payload);
  }

  @Mutation(() => Person)
  async deletePerson(
    @Args('_id', { type: () => String }) _id: MongooseSchema.Types.ObjectId,
  ) {
    return this.personService.delete(_id);
  }

  @ResolveField()
  async hobbies(
    @Parent() person: PersonDocument,
    @Args('populate') populate: boolean,
  ) {
    if (populate)
      await person
        .populate({ path: 'hobbies', model: Hobby.name })
        .execPopulate();

    return person.hobbies;
  }
}
Enter fullscreen mode Exit fullscreen mode
// person.model.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Field, ObjectType } from '@nestjs/graphql';
import { Document, Schema as MongooseSchema } from 'mongoose';

import { Hobby } from '../hobby/hobby.model';

@ObjectType()
@Schema()
export class Person {
  @Field(() => String)
  _id: MongooseSchema.Types.ObjectId;

  @Field(() => String)
  @Prop()
  name: string;

  @Field(() => [Hobby])
  @Prop({ type: [MongooseSchema.Types.ObjectId], ref: Hobby.name })
  hobbies: MongooseSchema.Types.ObjectId[] | Hobby[];
}

export type PersonDocument = Person & Document;

export const PersonSchema = SchemaFactory.createForClass(Person);
Enter fullscreen mode Exit fullscreen mode

This allows us to do request Person in this way:

Alt Text


Conclusion

As a developer, we combine different bricks of technologies to set up a viable ecosystem 🌍 for our project. In a javascript ecosystem, the advantage (or the disadvantage) is the abundance of brick and the possibility of combination. I hope to still have some motivation to be able to write a sequel to this blog post which explains the integration of this GraphQL API in a monorepo development environment and thus shares this data model between several applications and libraries.

Top comments (19)

Collapse
 
gastonmorixe profile image
Gaston Morixe • Edited

@Prop({ type: [Types.ObjectId], ref: Hobby.name })

Should be

@Prop({ type: [mongoose.Schema.Types.ObjectId], ref: Hobby.name })

Otherwise the references end up as strings instead of ObjectId.

Collapse
 
lotfi profile image
Lotfi

Thank you for your remark. Article updated!

Collapse
 
gastonmorixe profile image
Gaston Morixe

You are welcome!

Collapse
 
smolinari profile image
Scott Molinari

Nice article. I'd like to throw in another possible set of bricks for getting NestJS and Mongoose working. It's called Typegoose.

typegoose.github.io/typegoose/
(and corresponding NestJS Module for it: kpfromer.github.io/nestjs-typegoose/

Be aware though, using Mongoose with TypeScript is an adventure. They just moved their types to their own a few months ago and they are about 97% ok. But, there might be 3% where you'll be scratching your head. The Typegoose maintainer though works hard to work with the Mongoose team to get these type issues resolved and the upcoming 8.0 of Typegoose is shaping up to be pretty cool.

Hopefully, one day, we'll have a Mongoose written in TypeScript. Crossing my fingers!

Scott

Collapse
 
lotfi profile image
Lotfi

Interesting, I will check Typegoose, I didn't know it. Thank you for your comment.

Collapse
 
radovansurlak profile image
Radovan Surlak

Thank you for the article!

I have found an issue however - once you query for Person's hobbies without "populate: true", you will get errors from the GraphQL API.

If anyone knows how to allow the endpoint to return both IDs and populated documents, it would help out a lot

Collapse
 
lotfi profile image
Lotfi

There is a way, mentioned in NestJs doc, to create a combine type but it does not work with combining a scalar and object types. ex :

export const ResultUnion = createUnionType({
  name: 'ResultUnion',
  types: () => [Hobby, String],
});
Enter fullscreen mode Exit fullscreen mode

That's why I chose to add the populate attribute, so if we just want the ids, we set it to false, which will save db access for the mongoose populate operation.

Collapse
 
radovansurlak profile image
Radovan Surlak

Right, I was looking into that as well.

The issue for me was that if we use
populate: false, the resolver would be returning an array of IDs, which GraphQL would complain about, since it's not of type Hobby.

I have personally used an approach where I specify extra GraphQL field populatedHobbies of type [Hobby] and keep hobbies endpoint, which would return [ID]

Thread Thread
 
lotfi profile image
Lotfi

Good idea !

Collapse
 
jbool24 profile image
Justin Bellero

Anyone using this technique of overlaying Graphql/Mongoose models using the mongoose discriminators shcema type? I'd like to have one "users" collection that discriminates document data base on the user kind. Is it even possible to get that working? Am I trying to create a nightmare for myself?

I'm testing things out now but having issues with graphql resolution since the gql schema needs to implement union types (I think). I might be going about it wrong. Any suggestions?

Collapse
 
zyfyy profile image
zyfyy

Thank you so much!

Collapse
 
atifmahmood1566 profile image
AtifMahmood1566

Overall it is a good article for a beginner to understand about nest and graphql. The only problem i am facing while running the project is the following error.

Error: Cannot determine a GraphQL input type nullfor the "_id". Make sure your class is decorated with an appropriate decorator.

Collapse
 
lotfi profile image
Lotfi

Did you get the error on trying to run the project in the git repository?

Collapse
 
mehtabahmed176 profile image
Mehtab Ahmed

Thank you , one of the best article on NestJs and Graphql based api so far.

Collapse
 
aniganesh profile image
Aniganesh • Edited

Not sure if this has been pointed out but execPopulate has now been removed. Could you please caveat the execPopulate references?
stackoverflow.com/a/69444464/10032950
mongoosejs.com/docs/migrating_to_6...

Great article by the way. I found it useful

Collapse
 
dantereve profile image
Mathias Oviève

This blog post is exactly what I was looking for. You did a fantastic job explaining how to achieve this! Thank You! 🙇
If you find the time to write the sequel, I would be a happy reader.

Collapse
 
phyberapex profile image
PhyberApex

This is exactly what I was trying to do but couldn't figure out without a proper example. Thank you so much!

~Cheers

Collapse
 
fburner profile image
FBurner

Hi, i cant use find on list it shows me that the filter from the input doesnt match the signature

Collapse
 
lotfi profile image
Lotfi

Did you try to test the final projet on github repo ?