Source: awsome – nest
In Getting Started with NestJs (part 1), you’ve learned some important concepts about Nestjs. Now let’s start creating an application based on NestJs.
Nestjs, like Angular, provides CLI tools to help you initialize and develop your application.
$ npm install -g @nestjs/cli
$ nest new my-awesome-app
Copy the code
You get a directory structure like this:
After running NPM start, go to http://localhost:3000/ in your browser to see Hello World! .
The Controller and the Service
In Nestjs, all controllers and services are registered with the corresponding Module, like this:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Copy the code
In the MVC pattern, the controller gets data from the model. Correspondingly, in Nestjs, the controller handles the incoming request, calls the corresponding service to complete the business processing, and returns the response to the client.
You can usually create a controller using a CLI command:
$ nest g co cats
Copy the code
In this case, the CLI automatically generates a controller file and registers the controller with the corresponding module.
Unlike some other Node frameworks, Nestjs routing is not centrally managed, but is scattered across the controller, determined by the (optional) prefix declared in @controller() and any routes specified in the request decorator.
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}@Get(':id')
findOne(@Param('id') id: string) :string {
return this.catsService.getCat(); }}Copy the code
The above code, through the Get request to http://localhost:3000/cats/1 will call the findOne method.
If you need to prefix all requests, you can set GlobalPrefix directly in main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();
Copy the code
In Nestjs, the controller acts as the caller to the service, distributing the corresponding request to the corresponding service for processing.
In controller, we notice that we inject a CatsService instance into the constructor to call the methods in the corresponding service. This is how dependency injection is done in Nestjs – constructor injection.
Service can be seen as a layer between the controller and model, in the service call DAO (in Nestjs is various ORM tools or its own DAO layer) to implement database access, processing and integration of data.
import { Injectable } from '@nestjs/common';
@Injectable(a)export class CatsService {
getCat(id: string) :string {
return `This action returns ${id} cats`; }}Copy the code
The above code defines a service with @Injectable(), so you can inject it into other controllers or services.
Dtos and Pipe
In NestJS, dtos define objects that send data over a network, usually with a class-Validator or a Class-Transformer.
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
Copy the code
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto } from './dto';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat'; }}Copy the code
The DTO is defined for the request body, and the parameter type is restricted in the DTO. If the type passed in the body is not the required type, an error will be reported.
The class-Validator in DTO also needs to cooperate with PIPE to complete the verification function:
import {
PipeTransform,
ArgumentMetadata,
BadRequestException,
Injectable,
} from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToClass } from 'class-transformer'
import * as _ from 'lodash'
@Injectable(a)export class ValidationPipe implements PipeTransform<any> {
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata
if(! metatype || !this.toValidate(metatype)) {
return value
}
const object = plainToClass(metatype, value)
const errors = await validate(object)
if (errors.length > 0) {
const errorMessage = _.values(errors[0].constraints)[0]
throw new BadRequestException(errorMessage)
}
return value
}
private toValidate(metatype): boolean {
const types = [String.Boolean.Number.Array.Object]
return! types.find(type= > metatype === type)}}Copy the code
The pipe builds the original type based on metadata and object instances, and validates it with validate.
This pipe is usually used as a global pipe:
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Copy the code
Assuming we don’t have this pipe, then parameter validation will take place in the controller, breaking the single responsibility principle. Having this layer of PIPE helps us validate parameters, effectively reducing class complexity and improving readability and maintainability.
Interceptor and Exception filters
Writing the code here, we find that it returns a string, which is a little rough, and we need to wrap the correct and wrong responses. Suppose I want to return something like this:
{status: 1, message: 'request succeeded ', data: any} # Request failed {status: 1, message: string,}Copy the code
At this point, you can use the ideas of AOP to do just that. First, we need a global error slice layer to handle all exceptions; Second, if it is a successful request, the return result needs to be wrapped through a slice layer.
In Nestjs, when the request result is returned, the Interceptor will fire before the Exception Filter, so the Exception Filter will be the last chance to catch Exception. We use it as a slice layer for handling global errors.
import {
Catch,
ArgumentsHost,
HttpException,
ExceptionFilter,
HttpStatus,
} from '@nestjs/common'
@Catch(a)export class ExceptionsFilter implements ExceptionFilter {
async catch(exception, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
let message = exception.message
let isDeepestMessage = false
while(! isDeepestMessage) { isDeepestMessage = ! message.message message = isDeepestMessage ? message : message.message }const errorResponse = {
message: message || 'Request failed',
status: 1,}const status = exception instanceof HttpException ?
exception.getStatus() :
HttpStatus.INTERNAL_SERVER_ERROR
response.status(status)
response.header('Content-Type'.'application/json; charset=utf-8')
response.send(errorResponse)
}
}
Copy the code
The Interceptor wraps the result of a successful request:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
interface Response<T> {
data: T
}
@Injectable(a)export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map(rawData= > {
return {
data: rawData,
status: 0,
message: 'Request successful',}}))}}Copy the code
In the same way, the Interceptor and Exception Filter need to define it globally:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.useGlobalFilters(new ExceptionsFilter());
app.useGlobalInterceptors(new TransformInterceptor());
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
Copy the code
TypeORM
TypeORM is equivalent to the DAO layer in Nestjs. It supports multiple databases such as PostgreSQL, SQLite and even MongoDB (NoSQL). MySQL > create database; MySQL > create database;
> CREATE DATABASE test
Copy the code
Then install TypeORm:
$ npm install --save @nestjs/typeorm typeorm mysql
Copy the code
Usually, we have multiple environments with different database configurations, so we need to create a config folder to place the different database configurations:
// index.ts
import * as _ from 'lodash'
import { resolve } from 'path'
import productionConfig from './prod.config'
const isProd = process.env.NODE_ENV === 'production'
let config = {
port: 3000,
hostName: 'localhost',
orm: {
type: 'mysql',
host: 'localhost',
port: 3310,
username: 'root',
password: '123456',
database: 'test',
entities: [resolve(`./**/*.entity.ts`)],
migrations: ['migration/*.ts'],
timezone: 'UTC',
charset: 'utf8mb4',
multipleStatements: true,
dropSchema: false,
synchronize: true,
logging: true,}}if (isProd) {
config = _.merge(config, productionConfig)
}
export { config }
export default config
Copy the code
// prod.config.ts
import { resolve } from 'path'
export default {
port: 3210,
orm: {
type: 'mysql',
host: 'localhost',
port: 3312,
username: 'root',
password: '123456',
database: 'test',
entities: [resolve('./**/*.entity.js')],
migrations: ['migration/*.ts'],
dropSchema: false,
synchronize: false,
logging: false,}}Copy the code
You are advised not to enable orM synchronization in an online environment. If the type of an entity is different from that of the database, orM will drop and then add after you synchronize the database. This will cause data loss in the local test. The local test turns on the synchronize function so that the entity is automatically synchronized to the database after writing it.
Import TypeOrmModule in app.module.ts:
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { CatsController } from './cats/cats.controller'
import { CatsService } from './cats/cats.service'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import config from './config'
@Module({
imports: [
TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions),
],
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
Copy the code
We define a table named cat where id is an increment primary key:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('cat')
export class CatEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 50 })
name: string
@Column()
age: number
@Column({ length: 100, nullable: true })
breed: string
}
Copy the code
At this point, the Entity is synchronized to the database and the CAT table is visible in the test database.
When an entity is used by a module, it needs to be registered in the corresponding module. Use the forFeature() method to define which repositories should be registered in the current scope:
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { CatsController } from './cats/cats.controller'
import { CatsService } from './cats/cats.service'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import config from './config'
import { CatEntity } from './cats/cat.entity'
const ENTITIES = [
CatEntity,
]
@Module({
imports: [
TypeOrmModule.forRoot(config.orm as TypeOrmModuleOptions),
TypeOrmModule.forFeature([...ENTITIES]),
],
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
Copy the code
We can inject a CatRepository into the CatService using the @injectrepository () decorator:
import { Injectable } from '@nestjs/common' import { InjectRepository } from '@nestjs/typeorm' import { CatEntity } from './cat.entity' import { Repository } from 'typeorm' @Injectable() export class CatsService { constructor( @InjectRepository(CatEntity) private readonly catRepository: Repository<CatEntity>, ) { } async getCat(id: number): Promise<CatEntity[]> { return await this.catRepository.find({ id }) } }Copy the code
At that time to request http://localhost:3000/api/v1/cats/1 this API, it will return the following results:
{
"data": []."status": 0."message": "Request successful"
}
Copy the code
If you need to use more complex SQL statements in TypeORm, you can use createQueryBuilder to help you build:
this.catRepository
.createQueryBuilder('cat')
.Where('name ! = "" ')
.andWhere('age > 2')
.getMany()
Copy the code
If createQueryBuilder does not satisfy your requirements, you can write SQL statements directly using query:
this.catRepository.query(
'select * from cat where name ! =? and age > ? ',
[age],
)
Copy the code
Migration
In continuous delivery projects, where the project is iteratively coming online, database changes occur. For a working system, migration is often used to synchronize the database. TypeORM also comes with a CLI tool to help with database synchronization.
First create an ormconfig.json file locally:
{
"type": "mysql"."host": "localhost"."port": 3310."username": "root"."password": "123456"."database": "test"."entities": ["./**/*.entity.ts"]."migrations": ["migrations/*.ts"]."cli": {
"migrationsDir": "migrations"
},
"timezone": "UTC"."charset": "utf8mb4"."multipleStatements": true."dropSchema": false."synchronize": false."logging": true
}
Copy the code
The JSON file specifies the matching rules for the Entity and migration files, and the location of the migration file is configured in the CLI.
Run the following command to automatically generate the file 1563725408398-update-cat.ts under the migrations folder
$ ts-node node_modules/.bin/typeorm migration:create -n update-cat
Copy the code
1563725408398 in the file name is the timestamp of the generated file. This file will have up and down methods:
import {MigrationInterface, QueryRunner} from "typeorm";
export class updateCat1563725408398 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {}public async down(queryRunner: QueryRunner): Promise<any> {}}Copy the code
Up must contain the code needed to perform migration. Down must restore any up changes. There is a QueryRunner object in up and down. Use this object to perform all database operations. For example, we write a false data in the cat table:
import {MigrationInterface, QueryRunner} from "typeorm";
export class updateCat1563725408398 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`insert into cat (id, name, age, breed) values (2, 'test', 3, 'cat') `)}public async down(queryRunner: QueryRunner): Promise<any> {}}Copy the code
Json and run NPM run migration:run. The cat table will have a false data with id 2.
{
"scripts": {
"migration:run": "ts-node node_modules/.bin/typeorm migration:run",}}Copy the code
Note that the ormconfig.json file configuration is the local environment configuration, if you need to use the generated environment, you can write a new ormconfig-prod.json, Then run the migration name with –config ormconfig-prod.json.
One disadvantage of migration generated with TypeORm is that the SQL and the code are coupled together, and it is best if the SQL is a separate file and the migration script is a file, which makes it easier to run the SQL files directly in MySQL in special cases. Migrate: You can use db-migrate to manage migration scripts instead of TypeORm. Db-migrate will generate a JS script and two SQL files under the migration directory. One is down SQL.
For existing projects, if it is difficult to create entities from scratch based on the database, you can use the Typeorm-Model-Generator to automatically generate these entities. For example, run this command:
$typeorm - model - the generator - h 127.0.0.1-d arya -p 3310 -u root -x 123456 -e mysql -d test -o 'src/entities/' --noConfig true --cf param --ce pascal
Copy the code
The entity file cat.ts is generated under SRC /entities/.
import{BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne,OneToMany,OneToOne,PrimaryColumn,PrimaryGenera tedColumn,RelationId}from "typeorm";
@Entity("cat",{schema:"test", database:"test"})export class Cat {
@PrimaryGeneratedColumn({
type:"int",
name:"id"
})
id:number;
@Column("varchar",{
nullable:false,
length:50,
name:"name"
})
name:string;
@Column("int",{
nullable:false,
name:"age"
})
age:number;
@Column("varchar",{
nullable:true,
length:100,
name:"breed"
})
breed:string | null;
}
Copy the code
The log
The official log solution is given, but here we refer to Nestify and use Log4JS for log processing. The main reason is that Log4JS classifies, divides, and dumps logs to facilitate log management.
There are nine levels of logs in log4JS:
export enum LoggerLevel {
ALL = 'ALL',
MARK = 'MARK',
TRACE = 'TRACE',
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR',
FATAL = 'FATAL',
OFF = 'OFF',}Copy the code
The ALL and OFF levels are generally not used directly in business code. The remaining seven methods correspond to each Logger instance, that is, when these methods are called, the logs are rated.
For different log levels, the log is printed in log4JS in different colors, with the log output time and the corresponding module name:
Log4js.addLayout('Awesome-nest'.(logConfig: any) = > {
return (logEvent: Log4js.LoggingEvent): string= > {
let moduleName: string = ' '
let position: string = ' '
const messageList: string[] = []
logEvent.data.forEach((value: any) = > {
if (value instanceof ContextTrace) {
moduleName = value.context
if (value.lineNumber && value.columnNumber) {
position = `${value.lineNumber}.${value.columnNumber}`
}
return
}
if (typeofvalue ! = ='string') {
value = Util.inspect(value, false.3.true)
}
messageList.push(value)
})
const messageOutput: string = messageList.join(' ')
const positionOutput: string = position ? ` [${position}] ` : ' '
const typeOutput: string = ` [${ logConfig.type }] ${logEvent.pid.toString()} - `
const dateOutput: string = `${Moment(logEvent.startTime).format( 'YYYY-MM-DD HH:mm:ss', )}`
const moduleOutput: string = moduleName
? ` [${moduleName}] `
: '[LoggerService] '
let levelOutput: string = ` [${logEvent.level}] ${messageOutput}`
switch (logEvent.level.toString()) {
case LoggerLevel.DEBUG:
levelOutput = Chalk.green(levelOutput)
break
case LoggerLevel.INFO:
levelOutput = Chalk.cyan(levelOutput)
break
case LoggerLevel.WARN:
levelOutput = Chalk.yellow(levelOutput)
break
case LoggerLevel.ERROR:
levelOutput = Chalk.red(levelOutput)
break
case LoggerLevel.FATAL:
levelOutput = Chalk.hex('#DD4C35')(levelOutput)
break
default:
levelOutput = Chalk.grey(levelOutput)
break
}
return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow( moduleOutput, )}${levelOutput}${positionOutput}`}})Copy the code
In Log4JS, the issue of the log exit (i.e. where the log output goes) is resolved by the Appender:
Log4js.configure({
appenders: {
console: {
type: 'stdout',
layout: { type: 'Awesome-nest' },
},
},
categories: {
default: {
appenders: ['console'],
level: 'debug',}}})Copy the code
In config, logs of higher than debug level are output through the console.
Export a log class that exposes different levels of log methods in log4JS. The complete code is as follows:
import * as _ from 'lodash'
import * as Path from 'path'
import * as Log4js from 'log4js'
import * as Util from 'util'
import * as Moment from 'moment'
import * as StackTrace from 'stacktrace-js'
import Chalk from 'chalk'
export enum LoggerLevel {
ALL = 'ALL',
MARK = 'MARK',
TRACE = 'TRACE',
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR',
FATAL = 'FATAL',
OFF = 'OFF',}export class ContextTrace {
constructor(
public readonly context: string.publicreadonly path? :string.publicreadonly lineNumber? :number.publicreadonly columnNumber? :number.) {}
}
Log4js.addLayout('Awesome-nest'.(logConfig: any) = > {
return (logEvent: Log4js.LoggingEvent): string= > {
let moduleName: string = ' '
let position: string = ' '
const messageList: string[] = []
logEvent.data.forEach((value: any) = > {
if (value instanceof ContextTrace) {
moduleName = value.context
if (value.lineNumber && value.columnNumber) {
position = `${value.lineNumber}.${value.columnNumber}`
}
return
}
if (typeofvalue ! = ='string') {
value = Util.inspect(value, false.3.true)
}
messageList.push(value)
})
const messageOutput: string = messageList.join(' ')
const positionOutput: string = position ? ` [${position}] ` : ' '
const typeOutput: string = ` [${ logConfig.type }] ${logEvent.pid.toString()} - `
const dateOutput: string = `${Moment(logEvent.startTime).format( 'YYYY-MM-DD HH:mm:ss', )}`
const moduleOutput: string = moduleName
? ` [${moduleName}] `
: '[LoggerService] '
let levelOutput: string = ` [${logEvent.level}] ${messageOutput}`
switch (logEvent.level.toString()) {
case LoggerLevel.DEBUG:
levelOutput = Chalk.green(levelOutput)
break
case LoggerLevel.INFO:
levelOutput = Chalk.cyan(levelOutput)
break
case LoggerLevel.WARN:
levelOutput = Chalk.yellow(levelOutput)
break
case LoggerLevel.ERROR:
levelOutput = Chalk.red(levelOutput)
break
case LoggerLevel.FATAL:
levelOutput = Chalk.hex('#DD4C35')(levelOutput)
break
default:
levelOutput = Chalk.grey(levelOutput)
break
}
return `${Chalk.green(typeOutput)}${dateOutput} ${Chalk.yellow( moduleOutput, )}${levelOutput}${positionOutput}`
}
})
Log4js.configure({
appenders: {
console: {
type: 'stdout',
layout: { type: 'Awesome-nest' },
},
},
categories: {
default: {
appenders: ['console'],
level: 'debug',}}})const logger = Log4js.getLogger()
logger.level = LoggerLevel.TRACE
export class Logger {
statictrace(... args) { logger.trace(Logger.getStackTrace(), ... args) }staticdebug(... args) { logger.debug(Logger.getStackTrace(), ... args) }staticlog(... args) { logger.info(Logger.getStackTrace(), ... args) }staticinfo(... args) { logger.info(Logger.getStackTrace(), ... args) }staticwarn(... args) { logger.warn(Logger.getStackTrace(), ... args) }staticwarning(... args) { logger.warn(Logger.getStackTrace(), ... args) }staticerror(... args) { logger.error(Logger.getStackTrace(), ... args) }staticfatal(... args) { logger.fatal(Logger.getStackTrace(), ... args) }static getStackTrace(deep: number = 2): ContextTrace {
const stackList: StackTrace.StackFrame[] = StackTrace.getSync()
const stackInfo: StackTrace.StackFrame = stackList[deep]
const lineNumber: number = stackInfo.lineNumber
const columnNumber: number = stackInfo.columnNumber
const fileName: string = stackInfo.fileName
const extnameLength: number = Path.extname(fileName).length
let basename: string = Path.basename(fileName)
basename = basename.substr(0, basename.length - extnameLength)
const context: string = _.upperFirst(_.camelCase(basename))
return new ContextTrace(context, fileName, lineNumber, columnNumber)
}
}
Copy the code
So where you need to output the log, just call it like this:
Logger.info(id)
Copy the code
We don’t want each request to have its own log, so we can use this log as middleware:
import { Logger } from '.. /.. /shared/utils/logger'
export function logger(req, res, next) {
const statusCode = res.statusCode
const logFormat = `${req.method} ${req.originalUrl} ip: ${req.ip} statusCode: ${statusCode}`
next()
if (statusCode >= 500) {
Logger.error(logFormat)
} else if (statusCode >= 400) {
Logger.warn(logFormat)
} else {
Logger.log(logFormat)
}
}
Copy the code
Register in main.ts:
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.setGlobalPrefix('api/v1')
app.use(logger)
app.useGlobalFilters(new ExceptionsFilter())
app.useGlobalInterceptors(new TransformInterceptor())
app.useGlobalPipes(new ValidationPipe())
await app.listen(config.port, config.hostName)
}
Copy the code
ExceptionsFilter also logs the Exception caught in ExceptionsFilter:
export class ExceptionsFilter implements ExceptionFilter {
async catch(exception, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
Logger.error('exception'.JSON.stringify(exception))
let message = exception.message
let isDeepestMessage = false
while(! isDeepestMessage) { isDeepestMessage = ! message.message message = isDeepestMessage ? message : message.message }const errorResponse = {
message: message || 'Request failed',
status: 1,}const status = exception instanceof HttpException ?
exception.getStatus() :
HttpStatus.INTERNAL_SERVER_ERROR
Logger.error(
`Catch http exception at ${request.method} ${request.url} ${status}`,
)
response.status(status)
response.header('Content-Type'.'application/json; charset=utf-8')
response.send(errorResponse)
}
}
Copy the code
Such a basic logging system is almost complete. Of course, log4JS appenders also support the following:
-
DateFile: Logs are output to a file. The log file can be scrolled in a specific date mode. For example, the log file is output to default-2016-08-21.log today and default-2016-08-22.log tomorrow.
-
SMTP: Outputs logs to mails.
-
Mailgun: Outputs logs to the Mailgun through the Mailgun API.
-
LevelFilter You can pass the level filter.
-
You can see the full list here.
For example, the following configuration will output logs to a date-suffix file for 60 days:
Log4js.configure({
appenders: {
fileAppender: {
type: 'DateFile',
filename: './logs/prod.log',
pattern: '-yyyy-MM-dd.log',
alwaysIncludePattern: true,
layout: { type: 'Flash' },
daysToKeep: 60
}
},
categories: {
default: {
appenders: ['fileAppender'],
level: 'info'}}})Copy the code
CRUD
For general CRUD operations, the @nestJsx/CRUD library can be used in Nestjs to help reduce the amount of development.
First install the dependencies:
npm i @nestjsx/crud @nestjsx/crud-typeorm class-transformer class-validator --save
Copy the code
Then create a new dog.entity.ts:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('dog')
export class DogEntity {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 50 })
name: string
@Column()
age: number
@Column({ length: 100, nullable: true })
breed: string
}
Copy the code
In dog.service.ts, just write the following lines:
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm'
import { DogEntity } from './dog.entity'
@Injectable()
export class DogsService extends TypeOrmCrudService<DogEntity> {
constructor(@InjectRepository(DogEntity) repo) {
super(repo)
}
}
Copy the code
In dog.controller.ts, use the @crud help to automatically generate the API:
import { Controller } from '@nestjs/common'
import { Crud, CrudController } from '@nestjsx/crud'
import { DogEntity } from './dog.entity'
import { DogsService } from './dogs.service'
@Crud({
model: {
type: DogEntity,
},
})
@Controller('dogs')
export class DogsController implements CrudController<DogEntity> {
constructor(public service: DogsService) {}}Copy the code
At this point, you can follow the API rules in the @NestJsx/CRUD documentation to request the corresponding CRUD action. For example, asking the GET API /v1/dogs will return an array of all dogs; Ask GET API /v1/dogs/1 to return dog with ID 1.
reference
Using the CLI
The migration
Log4js for Node.js
nestify