NestJS + Mongo + Typegoose
This article explores NestJS, Mongo, and Typegoose and provides an example of how you can use MongoDB in your NestJS application in a headache-free way.
Join the DZone community and get the full member experience.
Join For FreeThree Options To Use Mongo With Node (And NestJS)
We will look into each and every one of them, and I will provide an example of how you can use MongoDB in your NestJS application in a headache-free way.
- The best tutorial I have found for NestJS + Mongoose is here. The issue I had with it is that I had to write the schema definitions and the typescript interfaces. If you are fine with writing everything two times, one for the Schema and one for the Document as Typescript Interface, maybe this is the best way to go!
- NestJS + TypeORM is where you can actually use TypeORM with MongoDB; however, I do not recommend this. If you want to learn more, I would ask you to read this blog post.
- NestJS + Typegoose — basically, it uses your domain objects to get the schema from them. And this is what this post is all about. There is a lot of documentation on how you can achieve that; however, I didn’t like most of it; it just looked like too much code. On top of that, ALL tutorials ALWAYS include using a DTO class, and I don’t see a reason to use DTO classes at all in 99% of the cases. DTOs are great, don’t get me wrong, there are many pros of using DTOs, but none of the tutorials on the internet actually explains why they want DTOs, and in fact, none of them need DTOs so I would like to write the easiest straightforward way with NestJS + MongoDB + and TypeGoose.
So, first of all, we will install NestJS CLI, NestJS is very, very similar to Angular, and even if you don't like Angular, trust me, you will like NestJS since I am actually the same! For a great beginner's tutorial for NestJS, you can read here find.
Let’s Start by Creating the NestJS App
npm i -g @nestjs/cli
Then create a NestJS project.
nest new nestjspoc-nest
cd nestjspoc-nest
// start the application using nodemon
npm run start:dev
Open the browser to localhost:3000 to verify hello world is displayed.
We will create a simple service and controller in a module. Let's say our applications will do something with users, and we will want to create a UserModule, which will hold the user domain objects, user services, and user controllers.
nest generate module user
nest generate service user
nest generate controller user
Now you should have a folder that has UserModule
, UserService
, and UserController
.
Which are almost empty.
For nest, we will use nestjs-typegoose because it makes everything even easier.
npm install — save nestjs-typegoose
The typegoose has several peer dependencies, so we need to install them as well. We have the two nestjs dependencies we already have, but we need the other two.
"@typegoose/typegoose": "^6.0.0",
"@nestjs/common": "^6.3.1",
"@nestjs/core": "^6.3.1",
"mongoose": "^5.5.13"
Once you are done, your package.json should look like this:
{
"name": "nestjspoc",
"version": "0.0.1",
"description": "",
"author": "",
"license": "MIT",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "tslint -p tsconfig.json -c tslint.json",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^6.7.2",
"@nestjs/core": "^6.7.2",
"@nestjs/platform-express": "^6.7.2",
"nestjs-typegoose": "^7.0.0",
"rimraf": "^3.0.0",
"rxjs": "^6.5.3",
"@typegoose/typegoose": "^6.0.0",
"mongoose": "^5.5.13"
},
"devDependencies": {
"@nestjs/cli": "^6.9.0",
"@nestjs/schematics": "^6.7.0",
"@nestjs/testing": "^6.7.1",
"@types/express": "^4.17.1",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.5",
"@types/supertest": "^2.0.8",
"jest": "^24.9.0",
"prettier": "^1.18.2",
"supertest": "^4.0.2",
"ts-jest": "^24.1.0",
"ts-loader": "^6.1.1",
"ts-node": "^8.4.1",
"tsconfig-paths": "^3.9.0",
"tslint": "^5.20.0",
"typescript": "^3.6.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "./coverage",
"testEnvironment": "node"
}
}
The ones we added manually are in bold.
Well, this is the setup; let’s write some code.
Create your domain object user in a file user.ts, for example :
import {prop, Typegoose} from '@typegoose/typegoose';
export class User extends Typegoose {
@prop()
name?: string;
}
You see the @prop()
yup, you need this. You can learn more about validations and what you can do in the typegoose documentation.
Then let's create or update our UserService class.
import {Injectable} from '@nestjs/common';
import {User} from './domain/user';
import {InjectModel} from 'nestjs-typegoose';
import {ReturnModelType} from '@typegoose/typegoose';
@Injectable()
export class UserService {
constructor(@InjectModel(User) private readonly userModel: ReturnModelType<typeof User>) {
}
async createCustomUser(user: User) {
const createdUser = new this.userModel(user);
return await createdUser.save();
}
async listUsers(): Promise<User[] | null> {
return await this.userModel.find().exec();
}
}
Ok, the first magic is actually here!
You may notice the line @InjectModel(User) private readonly userModel: ReturnModelType
, this will give us a userModel
that we can use for our User type.
The createCustomUser
and listUsers
use this userModel
and I believe it is all clear HOW :)
Next, update our UserController
.
import {Body, Controller, Get, Post} from '@nestjs/common';
import {UserService} from './user.service';
import {User} from './domain/user';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Get('listusers')
async listUsers(): Promise<User[] | null> {
return await this.userService.listUsers();
}
@Post('createuser')
async createUser(@Body() cat: User): Promise<User> {
return await this.userService.createCustomUser(cat);
}
}
Nothing fancy here; we just inject our Service, and we call our two methods.
Some more magic!
There are two more magic lines that you need to add to your UserModule
and AppModule.
On the UserModule definition, we need this line — imports: [TypegooseModule.forFeature([User])]:
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import {User} from './domain/user';
import {TypegooseModule} from 'nestjs-typegoose';
@Module({
imports: [TypegooseModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
And on the AppModule
, we need the Mongoose configuration of the MongoDB connection string imports:
imports: [TypegooseModule.forRoot(‘mongodb://localhost:27017/nest’), UserModule],
import {Module} from '@nestjs/common';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {UserModule} from './user/user.module';
import {TypegooseModule} from 'nestjs-typegoose';
@Module({
imports: [TypegooseModule.forRoot('mongodb://localhost:27017/nest'),
UserModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
}
And yes, you need MongoDB to be running ;)
Well, that’s it!
Create a User:
curl -X POST http://localhost:3000/user/createuser -H ‘Content-Type: application/json’ -d ‘{ “name”: “Nayden Gochev” }’
And you will receive:
{“_id”:”5dc00795d9a25df587a1e5f9",”name”:”Nayden Gochev”,”__v”:0}
List All Users:
curl -X GET http://localhost:3000/user/listusers
Missing Bits
What is missing? Validation, Security, and Testing, of course, all of this — next time ;) now was the nest time.
Source Code
The full source code can be downloaded here.
Published at DZone with permission of Nayden Gochev. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments