NestjsЭто прогрессивный фреймворк для Node.Нижний уровень по умолчанию использует экспресс (который можно преобразовать в fastify через адаптер), может использовать все промежуточное ПО экспресс или fastify и отлично поддерживает TypeScript. Студенты, знакомые со Spring и Angular, могут быстро начать работу с Nestjs, который заимствует множество дизайнерских идей из Spring и Angular.
в начале написанияhello worldПрежде давайте рассмотрим наиболее важные дизайнерские идеи и концепции в Nestjs.
внедрение зависимости
Injection Dependency Injection (внедрение зависимостей, для краткости)DI) находится в объектно-ориентированномИнверсия контроля(Инверсия управления, именуемаяIoC) является наиболее распространенной реализацией, в основном используемой для уменьшения связанности кода. Мы используем пример, чтобы проиллюстрировать, что такое инверсия управления.
Предположим, вы хотите построить машину, и вам нужен двигатель и колеса:
import { Engine } from './engine'
import { Tire } from './tire'
class Car {
private engine;
private wheel;
constructor() {
this.engine = new Engine();
this.tire = new Tire();
}
}
В этот моментCarЭтот класс зависит отEngineиTire, конструктор должен не только присвоить зависимость внутренним свойствам текущего класса, но и создать экземпляр зависимости. Допустим, существует множество видовCarвсе использованоEngine, то необходимоEngineзаменитьElectricEngine, впадет в смущение, дергая за один волос и двигая всем телом.
Затем используйте IoC для его преобразования:
import { Engine } from './engine'
import { Tire } from './tire'
class Container {
private constructorPool;
constructor() {
this.constructorPool = new Map();
}
register(name, constructor) {
this.constructorPool.set(name, constructor);
}
get(name) {
const target = this.constructorPool.get(name);
return new target();
}
}
const container = new Container();
container.bind('engine', Engine);
container.bind('tire', Tire);
class Car {
private engine;
private tire;
constructor() {
this.engine = container.get('engine');
this.tire = container.get('tire');
}
}
В настоящее время,containerэквивалентноCarиEngine,Tireпересадочная станция междуCarНе нужно создавать экземпляр самостоятельноEngineилиTire,CarиEngine,TireМежду ними нет сильной связи.
Как видно из приведенного выше примера, перед использованием IoC,CarнеобходимостьEngineилиTireВы должны взять на себя инициативу по созданиюEngineилиTire, в это время правильноEngineилиTireконтроль за созданием и использованиемCarпод рукой.
После использования IoC,CarиEngineилиTireСвязь междуCarнеобходимостьEngineилиTireчас,IoC Containerбудет активно создавать этот объект дляCarиспользовать, в это времяCarПолучатьEngineилиTireПоведение активного сбора данных становится пассивным, и управление меняется на противоположное. когдаEngineилиTireлюбые изменения,CarЭто не будет затронуто, и развязка между ними завершена.
когда нам нужно проверитьCar, нам не нужноEngineилиTireвсеnewпостроить один разCar, просто поместите макетEngineилиTire, просто введите его в контейнер IoC.
Существует множество реализаций IoC, таких как Spring для Java, Laravel для PHP, Angular2+ для внешнего интерфейса и Nestjs для Node.
В Nestjs через@InjectableДекоратор регистрируется в контейнере IoC:
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
Внедрить в конструкторCatsServiceПример:
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
CatsServiceкакprivider, должен быть вmoduleзарегистрирован вmoduleПри запуске он будет анализироватьmoduleво всех зависимостях, когдаmoduleПри уничтожении,providerтакже будут уничтожены вместе.
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class ApplicationModule {}
модульный
Nestjs предоставляет модульную структуру для организации кода внутри одного домена в отдельные модули. Роль модульности заключается в четкой организации вашего приложения и расширении его с помощью внешних библиотек.
ModuleПучокcontroller,serviceиpipeи т. д. упакованы в связанные функциональные блоки, каждый из которых ориентирован на функциональную область, бизнес-сферу, рабочий процесс или общий инструмент.
Прошел в Nestjs@ModuleДекоратор объявляет модуль,@ModuleПринимает объект, описывающий свойства модуля:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { CoreModule } from './core/core.module';
@Module({
imports: [CoreModule],
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
каждый принадлежащий этому модулюcontroller,serviceи т. д. должны быть зарегистрированы в этом модуле, если вам нужно ввести другие модули или сторонние модули, вам необходимо зарегистрировать его вimports,пройти черезexportsсоответствующийservice,moduleПодождите, чтобы поделиться.
Аспектно-ориентированное программирование
Аспектно-ориентированное программирование (сокращенно АОП) предназначено в основном для извлечения аспектов из процесса бизнес-обработки и выполнения некоторых операций на определенном этапе и этапе для достижения цели DRY (не повторяйтесь). АОП является дополнением к ООП. Например, он может обрабатывать глобальные журналы и ошибки в определенном аспекте. Такой универсальный подход означает, что метод обработки АОП является относительно крупнозернистым.
В Nestjs АОП разделен на следующие части (по порядку):
- Middlewares
- Guards
- Перехватчики (до обработки потока)
- Pipes
- Перехватчики (после обработки потока)
- Фильтры исключений (если найдены какие-либо исключения)
Middlewares
Промежуточное ПО — это то же самое, что и экспресс-промежуточное ПО, вы можете напрямую использовать экспресс-промежуточное ПО:
import * as helmet from 'helmet'
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
cors: true,
logger: false,
})
app.use(helmet())
await app.listen(config.port, config.hostName, () => {
Logger.log(
`Flash API server has been started on http://${config.hostName}:${config.port}`,
)
})
}
Guards
Охранники, как и охранники маршрутов во внешних маршрутах, в первую очередь определяют, должен ли запрос обрабатываться обработчиком маршрута. Guard может знать контекстную информацию, которую нужно выполнить, поэтому по сравнению с промежуточным программным обеспечением Guard может точно знать, что выполнять.
Охранники выполняются после каждого промежуточного программного обеспечения, но перед перехватчиками и каналами.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request); // validateRequest 函数实现 Request 的验证
}
}
Interceptors
Перехватчики могут быть привязаны к каждой функции, которую необходимо выполнить, и перехватчик будет запускаться до или после выполнения функции. Вы можете преобразовать результат, возвращаемый после выполнения функции, расширить базовое поведение функции и т. д.
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { getFormatResponse } from '../../shared/utils/response'
export interface Response<T> {
data: T
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(map(getFormatResponse))
}
}
Pipes
Труба с@Injectable()Класс декоратора и реализуетPipeTransformинтерфейс. Обычно канал используется для передачи входных данныхконвертироватьдля желаемого вывода или обработкипроверять.
Ниже приведенValidationPipe,сотрудничатьclass-validatorиclass-transformer, параметры могут быть проверены более легко.
import {
PipeTransform,
ArgumentMetadata,
BadRequestException,
Injectable,
} from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToClass } from 'class-transformer'
@Injectable()
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) {
throw new BadRequestException('Validation failed')
}
return value
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object]
return !types.find(type => metatype === type)
}
}
Exception filters
Встроенные фильтры исключений отвечают за обработку всех выброшенных исключений во всем приложении, а также это последняя возможность для Nestjs перехватывать исключения перед ответом.
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
@Catch()
export class AnyExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
response
.status(status)
.json({
statusCode: exception.getStatus(),
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
DTO
Объект доступа к данным (DTO) для краткости — это простой контейнер для группы агрегированных данных, которые необходимо передавать через границы процесса или сети. Он не должен содержать бизнес-логику и ограничивать свое поведение такими действиями, как внутренняя проверка согласованности и базовая проверка.
В Nestjs это можно сделать с помощью интерфейсов TypeScript или простых классов. Сотрудничатьclass-validatorиclass-transformerВы можете легко проверить параметры, переданные из внешнего интерфейса:
import { IsString, IsInt, MinLength, MaxLength } from "class-validator";
import { ApiModelProperty } from '@nestjs/swagger'
export class CreateCatDto {
@ApiModelProperty()
@IsString()
@MinLength(10, {
message: "Name is too short"
})
@MaxLength(50, {
message: "Name is too long"
})
readonly name: string;
@ApiModelProperty()
@IsInt()
readonly age: number;
@ApiModelProperty()
@IsString()
readonly breed: string;
}
import { Controller, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
}
Если параметры в Body не соответствуют требованиям, об этом будет сообщено напрямуюValidation failedОшибка.
ORM
ORM — это аббревиатура от «Object/Relational Mapping», которая завершает работу реляционной базы данных с помощью синтаксиса объектов-экземпляров. Объектно-ориентированное программирование можно использовать для работы с реляционными базами данных через ORM.
В Java обычно есть слой DAO (Data Access Object, объект доступа к данным), который содержит различные методы работы с базой данных. С помощью его методов выполняются связанные операции с базой данных. Основная функция DAO — разделить бизнес-уровень и уровень данных, чтобы избежать связи между бизнес-уровнем и уровнем данных.
В Nestjs вы можете использовать TypeORM в качестве уровня DAO, который поддерживает MySQL/MariaDB/Postgres/CockroachDB/SQLite/Microsoft SQL Server/Oracle/MongoDB/NoSQL.
В typeORM таблица базы данных соответствует классу, а сущность создается путем определения класса. Entity — это класс, который сопоставляется с таблицей базы данных (или коллекцией при использовании MongoDB) через@Entity()помечать.
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
Приведенный выше код создаст следующие таблицы базы данных:
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| firstName | varchar(255) | |
| lastName | varchar(255) | |
| isActive | boolean | |
+-------------+--------------+----------------------------+
использовать@InjectRepository()Декоратор вводит соответствующийRepository, вы можете использовать этоRepositoryВыполните некоторые операции с базой данных над объектом.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return await this.userRepository.find();
}
}
Ссылаться на
Понимание внедрения зависимостей и инверсии управления
От Express до Nestjs: расскажите об идеях дизайна и использовании Nestjs.