# Nest JS with Mongo DB Managing Relationships Part-4


![](./index.png)

# Nest JS with Mongo DB Managing Relationships Part-4

| Blog   |      Name      |  Link |
|----------|:-------------:|------:|
| Part-1 | Nest JS Building REST APIs using Mongo DB Part-1  | https://devs.tkssharma.com/blog/nestjs-building-restful-apis-with-mongodb |
| Part-2 | Nest JS APIs with mongoose Mongo DB Database Part-2  | https://devs.tkssharma.com/blog/how-to-build-nestjs-apis-with-mongodb-mongoose |
| Part-2 | Nest JS APIs with mongoose Mongo DB Database Part-2  | https://devs.tkssharma.com/blog/how-to-build-nestjs-apis-with-mongodb-mongoose |
|Part-3| Nest JS Building Auth Service with JWT Tokens Part-3 | https://devs.tkssharma.com/blog/how-to-build-nestjs-auth-service-using-token-based-authentication|
|Part-4| |build-nestjs-apis-with-mongoose-models-and-relationships|

In this post, I will  demonstrate how to kickstart a simple RESTful APIs with NestJS from a newbie's viewpoint.

Github Link 
https://github.com/tkssharma/blogs/tree/master/nestjs-rest-apis-docs


## What is NestJS?

As described in the [Nestjs](https://nestjs.com) website, Nestjs is *a progressive Node.js framework for building efficient, reliable and scalable server-side applications.* 

Nestjs combines the best programming practice and the cutting-edge techniques  from the NodeJS communities. 

* A lot of NestJS concepts are heavily inspired by the effort of the popular frameworks in the world, esp.  [Angular](https://www.angular.io) .
* Nestjs hides the complexities of web programming in NodeJS, it provides a common abstraction of the web request handling,  you are free to choose [Expressjs](https://expressjs.com/) or  [Fastify](https://www.fastify.io) as the background engine.
* Nestjs provides a lot of third party project integrations, from database operations, such as Mongoose, TypeORM, etc. to Message Brokers, such as Redis,   RabbitMQ, etc.

If you are new to Nestjs like me but  has some experience of  [Angular](https://www.angular.io) , [TypeDI](https://github.com/typestack/typedi#usage-with-typescript) or [Spring WebMVC](http://www.spring.io), bootstraping a Nestjs project is really a piece of cake. 


## Generating a NestJS project

Make sure you have installed the latest [Nodejs](https://nodejs.org/en/). 

```bash
npm i -g @nestjs/cli
```

When it is finished, there is a `nest` command available in the `Path`. The usage of `nest` is similar with `ng` (Angular CLI), type `nest --help` in the terminal to list help for all commands.

```bash
❯ nest --help
Usage: nest <command> [options]

Options:
  -v, --version                                   Output the current version.
  -h, --help                                      Output usage information.

Commands:
  new|n [options] [name]                          Generate Nest application.
  build [options] [app]                           Build Nest application.
  start [options] [app]                           Run Nest application.
  info|i                                          Display Nest project details.
  update|u [options]                              Update Nest dependencies.
  add [options] <library>                         Adds support for an external library to your project.
  generate|g [options] <schematic> [name] [path]  Generate a Nest element.
    Available schematics:
      ┌───────────────┬─────────────┐
      │ name          │ alias       │
      │ application   │ application │
      │ class         │ cl          │
      │ configuration │ config      │
      │ controller    │ co          │
      │ decorator     │ d           │
      │ filter        │ f           │
      │ gateway       │ ga          │
      │ guard         │ gu          │
      │ interceptor   │ in          │
      │ interface     │ interface   │
      │ middleware    │ mi          │
      │ module        │ mo          │
      │ pipe          │ pi          │
      │ provider      │ pr          │
      │ resolver      │ r           │
      │ service       │ s           │
      │ library       │ lib         │
      │ sub-app       │ app         │
      └───────────────┴─────────────┘
```

 Now generate a Nestjs project via:

```bash
nest new nestjs-sample
```

Open it in your favorite IDEs, such as [Intellij WebStorm](https://www.jetbrains.com/webstorm/) or [VSCode](https://code.visualstudio.com/). 

## Exploring the project files

Expand the project root, you will see the following like tree nodes.

```bash
.
├── LICENSE
├── nest-cli.json
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

```



The default structure of this project is very similar with the one generated by Angular CLI.

* *src/main.ts* is the entry file of this application.
* *src/app\** is the top level component in a nest application.  
  * There is an a*pp.module.ts* is a Nestjs `Module` which is similar with Angular `NgModule`, and used to organize codes in the logic view.
  * The *app.service.ts* is an `@Injectable` component, similar with the service in [Angular](https://spring.io) or Spring's Service, it is used for handling business logic. A service is annotated with `@Injectable`.
  * The *app.controller.ts* is the controller of MVC, to handle incoming request, and responds the handled result back to client. The annotatoin `@Controller()` is similar with Spring MVC's `@Controller`.
  * The *app.controller.spec.ts* is test file for *app.controller.ts*. Nestjs uses [Jest](https://jestjs.io/) as testing framework.
* *test* folder is for storing e2e test files.


# Dealing with model relations

We have added authentication in our application in the last post,  you maybe have a question, can I  add some fields to `Post` document and remember the user who created it and the one who updated it at the last time.

When I come to the `Post` model, and try to add fields to setup the auditors, I can not find a simple way to do this. After researching and consulting from the Nestjs channel in Discord, I was told that the `@nestjs/mongoose` can not deal with the relations between Documents.

There are some suggestions I got from the community.

* Use  [Typegoose](https://github.com/typegoose/typegoose) instead of `@nestjs/mongoose`, check the [typegoose doc](https://typegoose.github.io/typegoose/) for more details. More effectively, there is a [nestjs-typegoose](https://github.com/kpfromer/nestjs-typegoose) to assist you to bridge typegoose to the Nestjs world.
* Give up `@nestjs/mongoose`  and turn back to use the raw `mongoose` APIs instead.

I have some experience of express and mongoose written in legacy ES5, so in this post I will try to switch to use the pure Mongoose API to replace the modeling codes we have done in the previous post. With the help  of `@types/mongoose`, it is easy to apply static types on the mongoose schemas , documents and models.

## Redefining the models with Mongoose API

We will follow the following steps to clean the codes of  models one by one .

1. Clean the document definition interface.
2.  Redefine the schema for related documents using Mongoose APIs.
3. Define mongoose Models and provide them in the Nestjs IOC engine. 
4. Create a custom provider for connecting to Mongo  using Mongoose APIs.
5. Remove the `@nestjs/mongoose` dependency finally.

Firstly let's have a look at `Post`, in the `post.model.ts`, fill the following content:

```typescript
import { Document, Schema, SchemaTypes } from 'mongoose';
import { User } from './user.model';

export interface Post extends Document {
  readonly title: string;
  readonly content: string;
  readonly createdBy?: Partial<User>;
  readonly updatedBy?: Partial<User>;
}

export const PostSchema = new Schema(
  {
    title: SchemaTypes.String,
    content: SchemaTypes.String,
    createdBy: { type: SchemaTypes.ObjectId, ref: 'User', required: false },
    updatedBy: { type: SchemaTypes.ObjectId, ref: 'User', required: false },
  },
  { timestamps: true },
);
```

The `PostSchema` is defined by type-safe way, all supports can be found in `SchemeTypes` while navigating it.  The `createdBy` and `updatedBy` is a reference of `User` document. The `{ timestamps: true }` will append `createdAt` and `updatedAt` to  the document  and fill these two fields the current timestamp automatically  when saving and updating the documents.

Create a `database.providers.ts` file to declare the `Post` model. We also create a provider for Mongo connection.

```typescript
import { PostSchema, Post } from './post.model';
import {
  DATABASE_CONNECTION,
  POST_MODEL
} from './database.constants';

export const databaseProviders = [
  {
    provide: DATABASE_CONNECTION,
    useFactory: (): Promise<typeof mongoose> =>
      connect('mongodb://localhost/blog', {
        useNewUrlParser: true,
        useUnifiedTopology: true,
      }),
  },
  {
    provide: POST_MODEL,
    useFactory: (connection: Connection) =>
      connection.model<Post>('Post', PostSchema, 'posts'),
    inject: [DATABASE_CONNECTION],
  },
//...
];

```

> More info about creating custom providers, check the [custom providers](https://docs.nestjs.com/fundamentals/custom-providers) chapter of the official docs.

For the convenience of using the injection token, create a `database.constant.ts` file to define series of constants for further uses.

```type
export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';
export const POST_MODEL = 'POST_MODEL';
export const USER_MODEL = 'USER_MODEL';
export const COMMENT_MODEL = 'COMMENT_MODEL';
```

Create  a `database.module.ts` file, and define a  `Module` to collect the Mongoose related resources.

```typescript
@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}
```

To better organize the codes, move all model  related codes into the `database` folder.

Import `DatabaseModule` in the `AppModule`.

```typescript
@Module({
  imports: [
    DatabaseModule,
//...
})
export class AppModule {}
```

Now in the `post.service.ts`, change the injecting `Model<Post>` to the following.

```typesc
 constructor(
    @Inject(POST_MODEL) private postModel: Model<Post>,
    //...
    ){...}
```

In the test, change the injection token from class name to the constant value we defined, eg. 

```typescript
module.get<Model<Post>>(POST_MODEL)
```

Similarly, update the `user.model.ts` and related codes.

```typescript
//database/user.model.ts
export interface User extends Document {
  readonly username: string;
  readonly email: string;
  readonly password: string;
  readonly firstName?: string;
  readonly lastName?: string;
  readonly roles?: RoleType[];
}

const UserSchema = new Schema(
  {
    username: SchemaTypes.String,
    password: SchemaTypes.String,
    email: SchemaTypes.String,
    firstName: { type: SchemaTypes.String, required: false },
    lastName: { type: SchemaTypes.String, required: false },
    roles: [
      { type: SchemaTypes.String, enum: ['ADMIN', 'USER'], required: false },
    ],
    //   createdAt: { type: SchemaTypes.Date, required: false },
    //   updatedAt: { type: SchemaTypes.Date, required: false },
  },
  { timestamps: true },
);

UserSchema.virtual('name').get(function() {
  return `${this.firstName} ${this.lastName}`;
});

export const userModelFn = (conn: Connection) =>
  conn.model<User>('User', UserSchema, 'users');


//database/role-type.enum.ts
export enum RoleType {
  ADMIN = 'ADMIN',
  USER = 'USER',
}

//database/database.providers.ts
export const databaseProviders = [
    //...
  {
    provide: USER_MODEL,
    useFactory: (connection: Connection) => userModelFn(connection),
    inject: [DATABASE_CONNECTION],
  },
];

//user/user.service.ts
@Injectable()
export class UserService {
  constructor(@Inject(USER_MODEL) private userModel: Model<User>) {}
  //...	
}
```

Create  another model `Comment`, as sub document of `Post`. A comment holds a reference of Post doc.

```typescript
export interface Comment extends Document {
  readonly content: string;
  readonly post?: Partial<Post>;
  readonly createdBy?: Partial<User>;
  readonly updatedBy?: Partial<User>;
}

export const CommentSchema = new Schema(
  {
    content: SchemaTypes.String,
    post: { type: SchemaTypes.ObjectId, ref: 'Post', required: false },
    createdBy: { type: SchemaTypes.ObjectId, ref: 'User', required: false },
    updatedBy: { type: SchemaTypes.ObjectId, ref: 'User', required: false },
  },
  { timestamps: true },
);
```

Register it in `databaseProviders`.

```typescript
export const databaseProviders = [
  //...
  {
    provide: COMMENT_MODEL,
    useFactory: (connection: Connection) =>
      connection.model<Post>('Comment', CommentSchema, 'comments'),
    inject: [DATABASE_CONNECTION],
  },
 ]
```

Update the `PostService` ,  add two methods.

```typescript
//post/post.service.ts
export class PostService {
  constructor(
    @Inject(POST_MODEL) private postModel: Model<Post>,
    @Inject(COMMENT_MODEL) private commentModel: Model<Comment>
  ) {}
    //...
    //  actions for comments
  createCommentFor(id: string, data: CreateCommentDto): Observable<Comment> {
    const createdComment = this.commentModel.create({
      post: { _id: id },
      ...data,
      createdBy: { _id: this.req.user._id },
    });
    return from(createdComment);
  }

  commentsOf(id: string): Observable<Comment[]> {
    const comments = this.commentModel
      .find({
        post: { _id: id },
      })
      .select('-post')
      .exec();
    return from(comments);
  }
}    
```

The `CreateCommentDto` is a POJO to collect the data from request body.

```typescript
//post/create-comment.dto.ts
export class CreateCommentDto {
  readonly content: string;
}
```

Open `PostController`,  add two methods.

```typescript
export class PostController {
  constructor(private postService: PostService) {}

  //...
  @Post(':id/comments')
  createCommentForPost(
    @Param('id') id: string,
    @Body() data: CreateCommentDto,
  ): Observable<Comment> {
    return this.postService.createCommentFor(id, data);
  }

  @Get(':id/comments')
  getAllCommentsOfPost(@Param('id') id: string): Observable<Comment[]> {
    return this.postService.commentsOf(id);
  }
}
```

In the last post, we created authentication, to protect the saving and updating operations, you can set `JwtGuard` on the methods of the controllers.

But if we want to control the access in details, we need to consider `Authorization`, most of time, it is simple to implement it by introducing RBAC. 

## Role based access control

Assume there are two roles defined in this application, `USER` and `ADMIN`. In fact, we have already defined an enum class to archive this purpose.

Nestjs provide a simple way to set metadata by decorator on methods. 

```typescript
import { SetMetadata } from '@nestjs/common';
import { RoleType } from '../database/role-type.enum';
import { HAS_ROLES_KEY } from './auth.constants';

export const HasRoles = (...args: RoleType[]) => SetMetadata(HAS_ROLES_KEY, args);
```

Create specific `Guard` to read the metadata and compare the user object in request and decide if allow  user to access the controlled resources.

```typescript
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const roles = this.reflector.get<RoleType[]>(
      HAS_ROLES_KEY,
      context.getHandler(),
    );
    if (!roles) {
      return true;
    }

    const { user }= context.switchToHttp().getRequest() as AuthenticatedRequest;
    return user.roles && user.roles.some(r => roles.includes(r));
  }
}
```

For example, we require a  `USER`  role to create  a `Post` document.

```typescript
export class PostController {
  constructor(private postService: PostService) {}
    
  @Post('')
  @UseGuards(JwtAuthGuard, RolesGuard)
  @HasRoles(RoleType.USER, RoleType.ADMIN)
  createPost(@Body() post: CreatePostDto): Observable<BlogPost> {
    //...
  }
    
}    
```

You can add other rules on the resource access, such as a `USER` role is required to update a ` Post`, and  `ADMIN`  is to delete a `Post`.

## Adding auditing info

We have added roles to control access the resources, now we can save the current user who is creating the post or update the post.

There is a barrier when we wan to read the authenticated user from request and set it to fields `createdBy` and `updatedBy` in `PostService`, the `PostService` is singleton scoped, you can not inject a request in it.  But you can declare the `PostService` is `REQUEST` scoped, thus injecting a request instance is possible.

```typescript
@Injectable({ scope: Scope.REQUEST })
export class PostService {
  constructor(
    @Inject(POST_MODEL) private postModel: Model<Post>,
    @Inject(COMMENT_MODEL) private commentModel: Model<Comment>,
    @Inject(REQUEST) private req: AuthenticatedRequest,
  ) {}
    
  //...
  save(data: CreatePostDto): Observable<Post> {
    const createPost = this.postModel.create({
      ...data,
      createdBy: { _id: this.req.user._id },
    });
    return from(createPost);
  }
 
  update(id: string, data: UpdatePostDto): Observable<Post> {
    return from(
      this.postModel
        .findOneAndUpdate(
          { _id: id },
          { ...data, updatedBy: { _id: this.req.user._id } },
        )
        .exec(),
    );
  }
 
 //  actions for comments
  createCommentFor(id: string, data: CreateCommentDto): Observable<Comment> {
    const createdComment = this.commentModel.create({
      post: { _id: id },
      ...data,
      createdBy: { _id: this.req.user._id },
    });
    return from(createdComment);
  }    
}    
```

As a convention in Nestjs, you have to make `PostController` available in the `REQUEST` scoped.

```typescript
@Controller({path:'posts', scope:Scope.REQUEST})
export class PostController {...}
```

In the test codes, you have to `resolve` to replace `get` to get the instance from Nestjs test harness.

```typescript
describe('Post Controller', () => {
  describe('Replace PostService in provider(useClass: PostServiceStub)', () => {
    let controller: PostController;

    beforeEach(async () => {
      const module: TestingModule = await Test.createTestingModule({
        providers: [
          {
            provide: PostService,
            useClass: PostServiceStub,
          },
        ],
        controllers: [PostController],
      }).compile();

      controller = await module.resolve<PostController>(PostController);// use resovle here....
    });
  ...
```

`PostService` also should be  changed to request scoped. 

```typescript
@Injectable({ scope: Scope.REQUEST })
export class PostService {...}
```

In the `post.service.spec.ts` , you have to update the mocking progress.

```typescript
describe('PostService', () => {
  let service: PostService;
  let model: Model<Post>;
  let commentModel: Model<Comment>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PostService,
        {
          provide: POST_MODEL,
          useValue: {
            new: jest.fn(),
            constructor: jest.fn(),
            find: jest.fn(),
            findOne: jest.fn(),
            update: jest.fn(),
            create: jest.fn(),
            remove: jest.fn(),
            exec: jest.fn(),
            deleteMany: jest.fn(),
            deleteOne: jest.fn(),
            updateOne: jest.fn(),
            findOneAndUpdate: jest.fn(),
            findOneAndDelete: jest.fn(),
          },
        },
        {
          provide: COMMENT_MODEL,
          useValue: {
            new: jest.fn(),
            constructor: jest.fn(),
            find: jest.fn(),
            findOne: jest.fn(),
            updateOne: jest.fn(),
            deleteOne: jest.fn(),
            update: jest.fn(),
            create: jest.fn(),
            remove: jest.fn(),
            exec: jest.fn(),
          },
        },
        {
          provide: REQUEST,
          useValue: {
            user: {
              id: 'dummyId',
            },
          },
        },
      ],
    }).compile();

    service = await module.resolve<PostService>(PostService);
    model = module.get<Model<Post>>(POST_MODEL);
    commentModel = module.get<Model<Comment>>(COMMENT_MODEL);
  });
    
//...
```



## Run the application

Now we have done the clean work, run the application to make sure it works as expected.

```bash
> npm run start
```

Use `curl` to test the endpoints provided in the application.


```bash

$ curl http://localhost:3000/auth/login -d "{\"username\":\"hantsy\",\"password\":\"password\"}" -H "Content-Type:application/json" 

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJoYW50c3kiLCJzdWIiOiI1ZWYwYjdkNTRkMDY3MzIxMTQxODQ1ZjYiLCJlbWFpbCI6ImhhbnRzeUBleGFtcGxlLmNvbSIsInJvbGVzIjpbIlVTRVIiXSwiaWF0IjoxNTkyODM0MDE3LCJleHAiOjE1OTI4Mzc2MTd9.Jx53KIWHgyPADhLr-LhjW-iu1e8hD650e9nduGgJ8Bw"}

$ curl -X POST http://localhost:3000/posts -d "{\"title\":\"my title\",\"content\":\"my content\"}" -H "Content-Type:application/json" -H "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJoYW50c3kiLCJzdWIiOiI1ZWYwYjdkNTRkMDY3MzIxMTQxODQ1ZjYiLCJlbWFpbCI6ImhhbnRzeUBleGFtcGxlLmNvbSIsInJvbGVzIjpbIlVTRVIiXSwiaWF0IjoxNTkyODM0MDE3LCJleHAiOjE1OTI4Mzc2MTd9.Jx53KIWHgyPADhLr-LhjW-iu1e8hD650e9nduGgJ8Bw"

{"_id":"5ef0b7fe4d067321141845fc","title":"my title","content":"my content","createdBy":"5ef0b7d54d067321141845f6","createdAt":"2020-06-22T13:54:06.873Z","updatedAt":"2020-06-22T13:54:06.873Z","__v":0}

$ curl -X POST http://localhost:3000/posts/5ef0b7fe4d067321141845fc/comments -d "{\"content\":\"my content\"}" -H "Content-Type:application/json" -H "Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJoYW50c3kiLCJzdWIiOiI1ZWYwYjdkNTRkMDY3MzIxMTQxODQ1ZjYiLCJlbWFpbCI6ImhhbnRzeUBleGFtcGxlLmNvbSIsInJvbGVzIjpbIlVTRVIiXSwiaWF0IjoxNTkyODM0MDE3LCJleHAiOjE1OTI4Mzc2MTd9.Jx53KIWHgyPADhLr-LhjW-iu1e8hD650e9nduGgJ8Bw"

{"_id":"5ef0b8414d067321141845fd","post":"5ef0b7fe4d067321141845fc","content":"my content","createdBy":"5ef0b7d54d067321141845f6","createdAt":"2020-06-22T13:55:13.822Z","updatedAt":"2020-06-22T13:55:13.822Z","__v":0}

$ curl http://localhost:3000/posts/5ef0b7fe4d067321141845fc/comments
[{"_id":"5ef0b8414d067321141845fd","content":"my content","createdBy":"5ef0b7d54d067321141845f6","createdAt":"2020-06-22T13:55:13.822Z","updatedAt":"2020-06-22T13:55:13.822Z","__v":0}]

```

## One last thing

After cleaning up the codes, we do not need the  `@nestjs/mongoose` dependency, let's remove it.

```bash
npm uninstall --save @nestjs/mongoose
```

Grab [the source codes from my github](https://github.com/hantsy/nestjs-sample), switch to branch [feat/model](https://github.com/hantsy/nestjs-sample/blob/feat/model).
