Theses are my NestJS adventures, noted down as I go and learn about NestJS.
My plan
- getting started
- connect with MongoDB
- create a RESTful endpoint
- add debug lines
- with unit tests
- with integration tests
- create frontend
- basic boilerplate
- include GUI lib
- connect with backend
- integrated frontend code base with backend code base
- share functionality
- create auth service
- create users endpoint
- create integration test
- play around with GraphQL
- create another endpoint
- create an nested endpoint (e.g. invoice, invoice-line-items)
Issues
- fix: warning: DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option
{ useNewUrlParser: true }
to MongoClient.connect. DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option{ useUnifiedTopology: true }
to the MongoClient constructor. - using: nestjs/nest#530 (comment)
Assuming you have nothing installed, you will need:
- git - you just gotta have git
- nvm, node
- mongodb
- nest globally installed
npm i -g @nestjs/cli
- I use Visual Studio Code with these plugins: Code Spell Checker, Code Time, GitLens, Mermaid Editor, Prettier - Code formatter, vscode-mindmap, yUML, REST Client, Quokka.js, Browser Preview
After using nest new project-name
we have (used tree -I node_modules
)
├── README.md - documentation
├── nest-cli.json -
├── package-lock.json -
├── package.json - project configuration and scripts
├── src
│ ├── app.controller.spec.ts - test file
│ ├── app.controller.ts - Basic controller sample with a route.
│ ├── app.module.ts - The root module of the application.
│ ├── app.service.ts
│ └── main.ts - The entry file of the application
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json - Typescript build config
├── tsconfig.json - Typescript config
└── tslint.json - Typescript linting
Now is a good time to stick your stuff in git preferable using Conventional Commits
package.json
consists of a start script, but we want to see what is going on, so we add DEBUG=*
before the start script
$ npm start
> nestjs@0.0.1 start /nestjs
> DEBUG=* nest start
express:application set "x-powered-by" to true +0ms
express:application set "etag" to 'weak' +2ms
express:application set "etag fn" to [Function: generateETag] +0ms
express:application set "env" to 'development' +1ms
express:application set "query parser" to 'extended' +0ms
express:application set "query parser fn" to [Function: parseExtendedQueryString] +0ms
express:application set "subdomain offset" to 2 +0ms
express:application set "trust proxy" to false +0ms
express:application set "trust proxy fn" to [Function: trustNone] +0ms
express:application booting in development mode +1ms
express:application set "view" to [Function: View] +0ms
express:application set "views" to '/nestjs/views' +0ms
express:application set "jsonp callback name" to 'callback' +0ms
[Nest] 19599 - 02/11/2019, 14:56:52 [NestFactory] Starting Nest application...
[Nest] 19599 - 02/11/2019, 14:56:52 [InstanceLoader] AppModule dependencies initialized +10ms
express:router use '/' query +26ms
express:router:layer new '/' +0ms
express:router use '/' expressInit +0ms
express:router:layer new '/' +0ms
express:router use '/' jsonParser +0ms
express:router:layer new '/' +1ms
express:router use '/' urlencodedParser +0ms
express:router:layer new '/' +0ms
[Nest] 19599 - 02/11/2019, 14:56:52 [RoutesResolver] AppController {/}: +4ms
express:router:route new '/' +2ms
express:router:layer new '/' +0ms
express:router:route get '/' +0ms
express:router:layer new '/' +0ms
[Nest] 19599 - 02/11/2019, 14:56:52 [RouterExplorer] Mapped {/, GET} route +9ms
express:router use '/' <anonymous> +9ms
express:router:layer new '/' +0ms
express:router use '/' <anonymous> +0ms
express:router:layer new '/' +0ms
[Nest] 19599 - 02/11/2019, 14:56:52 [NestApplication] Nest application successfully started +2ms
note it uses the factory pattern
Also NestJs uses Typescript, async/await
Hey look! a similar tutorial and another adding GraphQL
Why? We would like to type:
import idk from '@src/module'
and avoid: import idk from '../../../whatever/module'
Therefore we need to create an alias '@src' using module-alias and follow this tutorial;
- install
modules-alias
,npm i -s module-alias
- update
./tsconfig.json
:"baseUrl": "./src", "paths": { "@src/*": ["./*"] },
- add to
./package.json
:"_moduleAliases": { "@src": "dist" }
- add to
src/main.ts
:import 'module-alias/register';
Why? We don't want hard-coded values in the code, but in an external config file.
todo: investigate .ENV file
- create folder
src/config
- create file
src/config/constants.ts
:
export const KEYS = {
mongodb_connection_uri: 'mongodb://localhost:27017/nestjs',
};
Why? I'm used to using mongoose as an ORM
I used @nestjs/mongoose
using this tutorial. Using this commit you can get it up and running. I've updated this Readme.
We need to type our objects, hence we need interfaces to tell Typescript what is what
- add module
products
by typingnest g module product
- create
src/products/products.interface.ts
:
import { Document } from 'mongoose';
export interface Product {
title: string;
brand: string;
currentPrice: number;
}
export interface ProductDocument extends Document, Product {}
We need to tell Mongo what the structure is of our Document
- create
src/products/products.schema.ts
:
import { ObjectId } from 'mongodb';
import { Model, Schema } from 'mongoose';
import { ProductDocument } from './products.interface';
export const ProductSchema = new Schema({
_id: { type: ObjectId, auto: true },
title: String,
brand: String,
currentPrice: Number,
});
export type ProductModel = Model<ProductDocument>; // todo: do we need ProductDocument?
- update
src/products/products.module.ts
:
import { Module } from '@nestjs/common';
import { DatabaseModule } from '@src/database/database.module';
import { productsProviders } from '@src/products/products.providers';
@Module({
imports: [DatabaseModule],
providers: [...productsProviders],
})
export class ProductsModule {}
- create file
src/products/products.controller.ts
:
import { Controller, Get, Post } from '@nestjs/common';
@Controller('products')
export class ProductsController {
@Post()
create(): string {
return 'This action adds a new product';
}
@Get()
findAll(): string {
return 'This action returns all products';
}
}
- update
src/app.module.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ProductsController } from './products/products.controller'; // << add this line
import { DatabaseModule } from './database/database.module';
@Module({
imports: [ProductsModule, DatabaseModule],
controllers: [AppController, ProductsController], // << add productsController
providers: [AppService],
})
export class AppModule {}
- start your app using
npm run start:dev
and test your work:
if you get an error: Cannot find module 'dist/main.js'
, run npm run build
to refresh the dist folder.
- execute
nest g service products
:
CREATE /src/products/products.service.spec.ts (474 bytes)
CREATE /src/products/products.service.ts (92 bytes)
UPDATE /src/products/products.module.ts (357 bytes)
from here
A DTO ((Data Transfer Object) is an object that defines how the data will be sent over the network. We could determine the DTO schema by using TypeScript interfaces, or by simple classes. Nestjs recommend using classes.
DTOs and interfaces are not two distinct things. DTOs can be implemented as either interfaces or classes, and the latter is clearly recommended by the documentation. Either way, you create DTOs that define the shape of data being sent or received.
Interfaces can not compute properties or use decorators, whereas classes can. NestJS recommends to use classes because you can add decorators from class-validator
to properties such as @IsString()
or @Max(20)
. With interfaces such a validation would only be possible outside of the interface context.
- create
src/products/products/products.dto.ts
:
export class CreateItemDTO {
readonly title: string;
readonly brand: string;
readonly currentPrice: number;
}
- update
src/products/products/product.services.ts
:
import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { Product, ProductDocument } from './products.interface';
import { CreateItemDTO } from './products.dto';
@Injectable()
export class ProductsService {
constructor(
@InjectModel('Product')
private readonly productsModel: Model<ProductDocument>,
) {}
async create(product: CreateItemDTO): Promise<Product> {
const newItem = new this.productsModel(product);
return await newItem.save();
}
async findAll(): Promise<Product[]> {
return await this.productsModel.find();
}
async findOne(id: string): Promise<Product> {
return await this.productsModel.findOne({ _id: id });
}
async update(id: string, item: Product): Promise<Product> {
return await this.productsModel.findByIdAndUpdate(id, item, { new: true });
}
async delete(id: string): Promise<Product> {
return await this.productsModel.findByIdAndRemove(id);
}
}
- update
src/products/products.controller.ts
:
import {
Controller,
Post,
Get,
Put,
Delete,
Param,
Body,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateItemDTO } from './products.dto';
import { Product } from './products.interface';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Post()
create(@Body() createItemDTO: CreateItemDTO): Promise<Product> {
return this.productsService.create(createItemDTO);
}
@Get()
async findAll(): Promise<Product[]> {
return this.productsService.findAll();
}
@Get(':id')
findOne(@Param('id') id): Promise<Product> {
return this.productsService.findOne(id);
}
@Put(':id')
update(
@Body() updateProductDTO: CreateItemDTO,
@Param('id') id,
): Promise<Product> {
return this.productsService.update(id, updateProductDTO);
}
@Delete(':id')
delete(@Param('id') id): Promise<Product> {
return this.productsService.delete(id);
}
}
Now the applications works like this:
Request | Response |
---|---|
GET /products |
[] |
POST /products {"title":"IDK"} |
{"_id": "5dbdd07b8b15757b28fac944","title": "IDK", "__v": 0} |
GET /products |
[{"_id": "5dbdd07b8b15757b28fac944","title": "IDK", "__v": 0}] |
GET /products/5dbdd07b8b15757b28fac944 |
{"_id": "5dbdd07b8b15757b28fac944","title": "IDK", "__v": 0} |
PUT /products/5dbdd07b8b15757b28fac944 {"title":"WIP"} |
{"_id": "5dbdd07b8b15757b28fac944","title": "WIP", "__v": 0} |
DELETE /products/5dbdd07b8b15757b28fac944 |
{"_id": "5dbdd07b8b15757b28fac944","title": "WIP", "__v": 0} |
GET /products |
[] |
Ideally we would like to create an Angular app that sits inside of our current folder, but this would mean we have to set up webpack manually, move our current code to a separate folder and do lots of more configuration. Because currently I'm impatient I just create a new clean repo and enjoy the Angular/cli features while enabling CORS
A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
Nest framework TypeScript starter repository.
$ npm install
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.
- Author - Kamil Myśliwiec
- Website - https://nestjs.com
- Twitter - @nestframework
Nest is MIT licensed.