Вы находитесь на странице: 1из 45

КАК 

СТАТЬ АВТОРОМ

Keithla 12 февраля 2022 в 16:13

Пишем nest.js с нуля на typescript


JavaScript*, Node.JS*, TypeScript*

Из песочницы ✏ Технотекст 2021

Цитата из документации:

Nest предоставляет готовую архитектуру приложений, которая позволяет
разработчикам и командам создавать высокопроверяемые, масштабируемые, слабо
связанные и легко обслуживаемые приложения. Архитектура в значительной степени
вдохновлена Angular.

Nest построен на основе шаблона проектирования ­ dependency injection. Мы увидим как он
реализован в Nest и как это влияет на весь остальной код.

Для начала посмотрим на самый простой код для запуска приложения nestjs из
документации:

main.ts

import { NestFactory } from "@nestjs/core"; 
import { AppModule } from "./app.module"; 
  
async function bootstrap() { 
    const app = await NestFactory.create(AppModule); 
    await app.listen(3000); 

  
bootstrap();

Итак. NestFactory главный класс nest с чего все и начинается. Его метод create сканирует
существующие модули, которые доступны в дереве зависимостей из корневого модуля
AppModule, после чего сканирует зависимости полученных модулей в виде контроллеров,
сервисов и других модулей, и добавляет все это в контейнер. После сканирования
возвращает экземпляр приложения. При запуске метода listen происходит инициализация
сервера и регистрация роутеров, созданных в контроллере, с необходимыми для каждого
обратными вызовами, которые уже хранятся в контейнере. 

К созданию подобной функциональности мы придем чуть позже, а сначала посмотрим
какие у нас есть контроллер, провайдер и модуль.

app.controller.ts

import { Controller, Get, Post, Body, Param } from 'nestjs/common'; 
import { AppService } from './app.service'; 
  
@Controller() 
export class AppController { 
  constructor(private readonly appService: AppService) {} 
  
  @Get() 
  getHello(): string { 
    return this.appService.getHello(); 
  } 
  
  @Post('body/:id') 
  recieveBody(@Body() data: { recieveData: string }, @Param('id') id: string) { 
    return 'body: ' + data.recieveData + ' has been recieved and id: ' + id; 
  } 

Что интересно в этом коде, так это то, что создавая методы запроса, мы просто
навешиваем декораторы на функции, которые и становятся callbacks для роутеров. Мы не
знаем, когда они регистрируется и как именно, нам не нужно думать о реализации, все, что
мы делаем, это следуем заданной архитектуре. Мы говорим, что мы хотим сделать, а не
как, то есть, в качестве пользователей Nest, используем декларативный подход.
Соответственно мы реализуем данную функциональность в этой статье.

Также здесь есть зависимость AppService, которую Nest внедряет самостоятельно. Для нас
же, это рабочий код. Зависимости в Nest разрешаются по типу, и мы рассмотрим как
именно.

app.service.ts

import { Injectable } from 'nestjs/common'; 
  
@Injectable() 
export class AppService { 
  getHello(): string { 
    return 'Hello World!'; 
  } 

Здесь мы видим декоратор Injectable, который, по описанию в документации, определяет
AppService как класс, которым может управлять контейнер Nest, что является не совсем
правдой. На самом деле все, что он делает, это добавляет метаданные о времени жизни
класса. По умолчанию оно совпадает с временем жизни приложения, и сам Nest не
рекомендует изменять это поведение. Поэтому если вы не хотите это изменить, то
Injectable можно опустить. А управлять им контейнер Nest может только в том случае, если
он будет присутствовать в providers модуля, в котором он используется.

app.module.ts

import { Module } from 'nestjs/common'; 
import { AppController } from './app.controller'; 
import { AppService } from './app.service'; 
  
@Module({ 
  imports: [], 
  controllers: [AppController], 
  providers: [AppService], 
}) 
export class AppModule {}

Итак. Возвращаясь к main.ts, реализуем NestFactory класс.

Стоит оговориться, некоторые вспомогательные функции, вроде проверки на null и т.д., а
также интерфейсы будут опущены в статье, но будут находиться в исходном коде.

./core/nest­factory.ts

import { NestApplication } from "./nest‐application"; 
import { NestContainer } from "./injector/container"; 
import { InstanceLoader } from "./injector/instance‐loader"; 
import { DependenciesScanner } from "./scanner"; 
import { ExpressAdapter } from '../platform‐express/express.adapter'; 
  
export class NestFactoryStatic { 
    public async create(module: any) { 
        const container = new NestContainer(); 
    // Сканирует зависимости, создает экземпляры 
    // и внедряет их 
        await this.initialize(module, container); 
  
    // Инициализирует http сервер и регистрирует роутеры 
     // при запуске instance.listen(3000) 
        const httpServer = new ExpressAdapter() 
        container.setHttpAdapter(httpServer); 
        const instance = new NestApplication( 
            container, 
            httpServer, 
            applicationConfig, 
        ); 
        
        return instance; 
    } 
  
    private async initialize( 
        module: any, 
        container: NestContainer,
    ) { 
        const instanceLoader = new InstanceLoader(container) 
        const dependenciesScanner = new DependenciesScanner(container); 
  
        await dependenciesScanner.scan(module); 
        await instanceLoader.createInstancesOfDependencies(); 
    } 

  
  
/** 
 * Используйте NestFactory для создания экземпляра приложения. 
 * 
 * ### Указание входного модуля 
 * 
 * Передайте требуемый *root module* (корневой модуль) для приложения 
 * через параметр модуля. По соглашению он обычно называется 
 * `AppModule`. Начиная с этого модуля Nest собирает граф 
 * зависимостей и создает экземпляры классов, необходимых для запуска 
 * вашего приложения. 
 * 
 * @publicApi 
 */ 
export const NestFactory = new NestFactoryStatic(); 

Итак. Из кода мы видим, что сперва сканируются все существующие модули, а также их
зависимости, а после создается http сервер. Поэтому сейчас мы реализуем класс
DependenciesScanner. Он получится чуть больше, чем предыдущий, но не будем пугаться,
ведь ничего сложного, на самом деле, там нет.

./core/scanner.ts

import { MODULE_METADATA } from "../common/constants"; 
import { NestContainer } from "./injector/container"; 
import 'reflect‐metadata'; 
import { Module } from "./injector/module"; 
  
export class DependenciesScanner { 
  
    constructor(private readonly container: NestContainer) {} 
  
    public async scan(module: any) { 
        // Сначала сканирует все модули, которые есть в приложении, и добавляет их в контейне
        await this.scanForModules(module); 
        // После у каждого модуля сканирует зависимости, такие как Controllers и Providers
        await this.scanModulesForDependencies(); 
    } 
  
    public async scanForModules(module: any) { 
        // Добавляет модуль в контейнер и возвращает при этом его экземпляр 
        const moduleInstance = await this.insertModule(module); 
        // Получает модули, которые были импортированы в этот модуль в массив imports. 
        // Так как AppModule ‐ корневой модуль, то от него идет дерево модулей. 
        const innerModules = [...this.reflectMetadata(moduleInstance, MODULE_METADATA.IMPORTS
  
        // Перебирает внутренние модули этого модуля, чтобы сделать с ними тоже самое. 
        // То есть, происходит рекурсия. 
        for (const [index, innerModule] of innerModules.entries()) { 
            await this.scanForModules(innerModule) 
        } 
  
        return moduleInstance 
    } 
  
    /** 
     * Добавляет модуль в контейнер 
     */ 
    public async insertModule(module: any) { 
        return this.container.addModule(module); 
    } 
  
  
    /** 
     * Получает из контейнера все модули, и сканирует у них 
     * зависимости, которые хранятся в reflect объекте. 
     */ 
    public async scanModulesForDependencies() { 
        const modules: Map<string, Module> = this.container.getModules(); 
  
        for (const [token, { metatype }] of modules) { 
            await this.reflectAndAddImports(metatype, token); 
            this.reflectAndAddProviders(metatype, token); 
            this.reflectAndAddControllers(metatype, token); 
        } 
    } 
  
    public async reflectAndAddImports( 
        module: any, 
        token: string, 
    ) { 
        // Получает по модулю imports зависимости и добавляет их в контейнер 
        const modules = this.reflectMetadata(module, MODULE_METADATA.IMPORTS); 
        for (const related of modules) { 
            await this.container.addImport(related, token); 
        } 
    } 
  
    public reflectAndAddProviders( 
        module: any, 
        token: string, 
    ) { 
        // Получает по модулю providers зависимости и добавляет их в контейнер 
        const providers = this.reflectMetadata(module, MODULE_METADATA.PROVIDERS); 
        providers.forEach((provider: any) => 
            this.container.addProvider(provider, token), 
        ); 
    } 
  
    public reflectAndAddControllers(module: any, token: string) { 
        // Получает по модулю controllers зависимости и добавляет их в контейнер 
        const controllers = this.reflectMetadata(module, MODULE_METADATA.CONTROLLERS); 
        controllers.forEach((controller: any) => 
            this.container.addController(controller, token), 
        ); 
    } 
  
    /** 
     * Метод, который получает нужные зависимости по модулю и ключу зависимостей. 
     */ 
    public reflectMetadata(metatype: any, metadataKey: string) { 
        return Reflect.getMetadata(metadataKey, metatype) || []; 
    } 

Смотря на код, мы видим, что модули и их зависимости добавляются в контейнер, и это
возможно благодаря двум классам используемым здесь ­ NestContainer и Module. На самом
деле в контейнере хранятся модули как экземпляры класса Module, и их зависимости,
такие как другие модули, контроллеры и провайдеры, хранятся в Module, в таких
структурах данных как Map и Set. А сами зависимости, контроллеры и провайдеры,
являются экземплярами класса InstanceWrapper. 

Классы Module и InstanceWrapper в нашей реализации являются довольно простыми,
особенно второй, поэтому сначала реализуем наш контейнер.

./core/injector/container.ts

import { Module } from "./module"; 
import { ModuleTokenFactory } from "./module‐token‐factory"; 
import { AbstractHttpAdapter } from "../adapters"; 
  
export class NestContainer { 
    private readonly modules = new Map<string, Module>(); 
    private readonly moduleTokenFactory = new ModuleTokenFactory(); 
    private httpAdapter: AbstractHttpAdapter | undefined; 
  
    /** 
     * Создает экземпляр класса Module и сохраняет его в контейнер 
     */ 
    public async addModule(module: any) { 
        // Создает токен модуля, который будет являться его ключом Map, 
        // который и будет использоваться для проверки и получения этого модуля. 
        const token = this.moduleTokenFactory.create(module); 
  
        if (this.modules.has(module.name)) { 
            return; 
        } 
  
        const moduleRef = new Module(module); 
        moduleRef.token = token; 
        this.modules.set(token, moduleRef); 
  
        return moduleRef; 
    } 
  
    /** 
     * Возвращает все модули, для сканирования зависимостей, 
     * создания экземпляров этих зависимостей, и для использования в качестве callbacks 
     * при создании роутеров его контроллеров, с разрешенными зависимостями. 
     */ 
    public getModules(): Map<string, Module> { 
        return this.modules; 
    } 
  
    /** 
     * Контейнер также устанавливает и хранит единственный экземпляр http сервера, 
     * в нашем случае express. Этот метод вызывается в классе NestFactory. 
     */ 
    public setHttpAdapter(httpAdapter: any) { 
        this.httpAdapter = httpAdapter; 
    } 
  
    /** 
     * Будет вызван при создании роутеров в классе RouterExplorer. 
     */ 
    public getHttpAdapterRef() { 
        return this.httpAdapter; 
    }     
  
    /** 
     * При сканировании зависимостей для полученных модулей в DependenciesScanner, 
     * у них также берется токен, по которому здесь находится модуль, 
     * и с помощью своего метода добавляет к себе импортированный модуль. 
     */ 
    public async addImport( 
        relatedModule: any, 
        token: string, 
    ) { 
        if (!this.modules.has(token)) { 
            return; 
        } 
        const moduleRef = this.modules.get(token); 
        if (!moduleRef) { 
            throw Error('MODULE NOT EXIST') 
        } 
  
        const related = this.modules.get(relatedModule.name); 
        if (!related) { 
            throw Error('RELATED MODULE NOT EXIST') 
        } 
        moduleRef.addRelatedModule(related); 
    } 
  
    /** 
     * Также как и для импортированных модулей, подобная функциональность 
     * работает и для провайдеров. 
     */ 
    public addProvider(provider: any, token: string) { 
        if (!this.modules.has(token)) { 
            throw new Error('Module not found.'); 
        } 
        const moduleRef = this.modules.get(token); 
        if (!moduleRef) { 
            throw Error('MODULE NOT EXIST') 
        } 
        moduleRef.addProvider(provider) 
    } 
  
    /** 
     * Также как и для импортированных модулей, подобная функциональность 
     * работает и для контроллеров. 
     */ 
    public addController(controller: any, token: string) { 
        if (!this.modules.has(token)) { 
            throw new Error('Module not found.'); 
        } 
        const moduleRef = this.modules.get(token); 
        if (!moduleRef) { 
            throw Error('MODULE NOT EXIST') 
        } 
        moduleRef.addController(controller); 
    } 

Мы увидели здесь также класс ModuleTokenFactory, который создаёт токен, по которому
хранится модуль. На самом деле, здесь можно обойтись и обычным созданием
уникального id, например с помощью пакета uuid. Поэтому вы можете сильно не обращать
на это внимание, но, кому интересно, вот максимально приближенная реализация этого
класса к реализации Nest, только несколько упрощенная.

./core/injector/module­token­factory.ts

import hash from 'object‐hash'; 
import { v4 as uuid } from 'uuid'; 
import { Type } from '../../common/interfaces/type.interface'; 
  
export class ModuleTokenFactory {
    // Здесь хранятся данные о том, какие модули уже были отсканированы. 
    // На случай того, если один модуль является зависимостью у нескольких, 
    // чтобы не было дубликатов. 
    private readonly moduleIdsCashe = new WeakMap<Type<unknown>, string>() 
  
    public create(metatype: Type<unknown>): string { 
        const moduleId = this.getModuleId(metatype); 
        const opaqueToken = { 
            id: moduleId, 
            module: this.getModuleName(metatype), 
        }; 
        return hash(opaqueToken, { ignoreUnknown: true }); 
    } 
  
    public getModuleId(metatype: Type<unknown>): string { 
        let moduleId = this.moduleIdsCashe.get(metatype); 
        if (moduleId) { 
            return moduleId; 
        } 
        moduleId = uuid(); 
        this.moduleIdsCashe.set(metatype, moduleId); 
        return moduleId; 
    } 
  
    public getModuleName(metatype: Type<any>): string { 
        return metatype.name; 
    } 

Теперь рассмотрим класс Module.

./core/injector/module.ts

import { InstanceWrapper } from "./instance‐wrapper"; 
import { randomStringGenerator } from "../../common/utils/random‐string‐generator.util";
  
export class Module { 
    private readonly _imports = new Set<Module>(); 
    private readonly _providers = new Map<any, InstanceWrapper>(); 
    private readonly _controllers = new Map<string, InstanceWrapper>(); 
  
    private _token: string | undefined; 
  
    constructor( 
        private readonly module: any, 
    ) {} 
  
    get providers(): Map<string, any> { 
        return this._providers; 
    } 
  
    get controllers(): Map<string, any> { 
        return this._controllers;
    } 
  
    get metatype() { 
        return this.module; 
    } 
  
    get token() { 
        return this._token!; 
    } 
  
    set token(token: string) { 
        this._token = token; 
    } 
  
    public addProvider(provider: any) { 
        this._providers.set( 
            provider.name, 
            new InstanceWrapper({
              name: provider.name, 
              metatype: provider,
              instance: null, 
            }), 
        ) 
    } 
  
    public addController(controller: any) { 
        this._controllers.set( 
            controller.name, 
            new InstanceWrapper({
              name: controller.name, 
              metatype: controller, 
              instance: null, 
            }), 
        ); 
  
        this.assignControllerUniqueId(controller); 
    } 
  
    public assignControllerUniqueId(controller: any) { 
        Object.defineProperty(controller, 'CONTROLLER_ID', { 
          enumerable: false, 
          writable: false, 
          configurable: true, 
          value: randomStringGenerator(), 
        }); 
    } 
  
    public addRelatedModule(module: Module) { 
        this._imports.add(module); 
    } 

Комментарии здесь излишни. Все, что он делает, это хранит зависимости определенного
модуля, сам модуль и его токен.

Теперь рассмотрим еще более простой класс InstanceWrapper.

./core/injector/instance­wrapper.ts
import { Type } from '../../common/interfaces/type.interface'; 
  
export class InstanceWrapper<T = any> { 
    public readonly name: string;
    public metatype: Type<T> | Function; 
    public instance: any; 
    public isResolved = false 
  
    constructor(metadata: any) { 
        Object.assign(this, metadata); 
        this.instance = metadata.instance; 
        this.metatype = metadata.metatype; 
        this.name = metadata.name
    } 

При его создании к instance присваивается null. В дальнейшем, например, если контроллер
имеет зависимость в виде провайдера в его конструкторе, то при внедрении зависимостей,
экземпляр этого провайдера будет уже создан, и при создании экземпляра контроллера,
будет добавлен в его конструктор. Собственно так и разрешаются зависимости.
Собственно этим мы дальше и займемся.

Сейчас у нас есть функциональность сканирования модулей и их зависимостей. Модули
добавляются в контейнер, хранятся по созданным токеном в виде класса Module, в
котором они все и представлены и хранят свои зависимости, которые находятся в объекте
reflect, в структурах данных Map и Set.

А теперь снова вернемся к классу NestContainer и взглянем на его метод initialize

private async initialize( 
        module: Module, 
        container: NestContainer,
    ) { 
        const instanceLoader = new InstanceLoader(container) 
        const dependenciesScanner = new DependenciesScanner(container); 
  
        await dependenciesScanner.scan(module); 
        await instanceLoader.createInstancesOfDependencies();
на его метод initialize

Сейчас, когда мы просканировали модули, нам нужно создать экземпляры их
зависимостей. Поэтому сейчас реализуем класс InstanceLoader.

./core/injector/instance­loader.ts

import { NestContainer } from "./container"; 
import { Injector } from "./injector"; 
import { Module } from "./module"; 
  
export class InstanceLoader { 
    private readonly injector = new Injector(); 
  
    constructor(private readonly container: NestContainer) {} 
  
    public async createInstancesOfDependencies() { 
        const modules = this.container.getModules(); 
  
        await this.createInstances(modules); 
    } 
  
    /** 
     * Сначала создаются экземпляры провайдеров, 
     * потому что если они являются зависимостями контроллеров, 
     * при создании экземпляров для контроллеров, они уже должны 
     * существовать. 
     */ 
    private async createInstances(modules: Map<string, Module>) { 
        await Promise.all( 
            [...modules.values()].map(async module => { 
                await this.createInstancesOfProviders(module); 
                await this.createInstancesOfControllers(module); 
            }) 
        ) 
    } 
  
    private async createInstancesOfProviders(module: Module) { 
        const { providers } = module; 
        const wrappers = [...providers.values()]; 
        await Promise.all( 
            wrappers.map(item => this.injector.loadProvider(item, module)), 
        ) 
    } 
  
    private async createInstancesOfControllers(module: Module) { 
        const { controllers } = module; 
        const wrappers = [...controllers.values()]; 
        await Promise.all( 
            wrappers.map(item => this.injector.loadControllers(item, module)), 
        ) 
    } 

Тоже не сложный класс. Все, что она делает вызывает методы класса Injector. Что здесь
стоит отметить, что уже написано в комментарии к методу createInstances, это то, что
созданные экземпляры провайдеров будут добавляться в конструкторы соответствующих
контроллеров при создании их экземпляров. 

Сейчас рассмотрим класс Injector, который несколько интереснее остальных, и который и
производит внедрение зависимостей.

./core/injector/injector.ts

import { Module } from "./module"; 
import { InstanceWrapper } from './instance‐wrapper'; 
import { Type } from  '../../common/interfaces/type.interface'; 
  
export class Injector { 
  
    public async loadInstance<T>(
        wrapper: InstanceWrapper<T>, 
        collection: Map<string, InstanceWrapper>, 
        moduleRef: Module, 
    ) { 
        const { name } = wrapper;
  
        const targetWrapper = collection.get(name); 
        if (!targetWrapper) { 
            throw Error('TARGET WRAPPER NOT FOUNDED') 
        } 
        const callback = async (instances: unknown[]) => { 
            await this.instantiateClass( 
                instances, 
                wrapper, 
                targetWrapper, 
            ); 
        } 
        await this.resolveConstructorParams<T>( 
            wrapper, 
            moduleRef, 
            callback, 
          ); 
    } 
  
    public async loadProvider( 
        wrapper: any, 
        moduleRef: Module, 
    ) { 
        const providers = moduleRef.providers; 
        await this.loadInstance<any>( 
            wrapper, 
            providers, 
            moduleRef, 
        ); 
    } 
  
    public async loadControllers(
        wrapper: any, 
        moduleRef: Module, 
    ) { 
        const controllers = moduleRef.controllers; 
        await this.loadInstance<any>( 
            wrapper, 
            controllers, 
            moduleRef, 
        ); 
    } 
  
    /** 
     * design:paramtypes создается автоматически объектом reflect 
     * для зависимостей, указанных в конструкторе класса. 
     * Как видно, если провайдеру нужно разрешить зависимости, 
     * то они также должны быть провайдерами. 
     * callback, как видно из метода loadInstance, вызывает метод 
     * instantiateClass для найденных зависимостей в виде провайдеров. 
     */ 
    public async resolveConstructorParams<T>( 
        wrapper: InstanceWrapper<T>, 
        moduleRef: Module, 
        callback: (args: unknown[]) => void | Promise<void>, 
    ) { 
        const dependencies = Reflect.getMetadata('design:paramtypes', wrapper.metatype) 
    
        const resolveParam = async (param: Function, index: number) => { 
          try { 
            let providers = moduleRef.providers 
            const paramWrapper = providers.get(param.name); 
            return paramWrapper?.instance 
          } catch (err) { 
              throw err; 
          } 
        }; 
        const instances = dependencies ? await Promise.all(dependencies.map(resolveParam)) : 
        await callback(instances); 
    } 
  
    /** 
     * Создает экземпляр зависимости, которая хранится в InstanceLoader, 
     * как metatype, с ее зависимостями, которые являются провайдерами, 
     * и добавляет этот экземпляр в instance поле класса InstanceLoader, 
     * для дальнейшего извлечения при создании роутеров. 
     */ 
    public async instantiateClass<T = any>( 
        instances: any[], 
        wrapper: InstanceWrapper,
        targetMetatype: InstanceWrapper, 
    ): Promise<T> { 
        const { metatype } = wrapper; 
  
        targetMetatype.instance = instances 
            ? new (metatype as Type<any>)(...instances) 
            : new (metatype as Type<any>)(); 
        
        return targetMetatype.instance; 
    } 

Отлично. Теперь у нас есть просканированные модули, и созданные экземпляры
зависимостей. Идем дальше.

Сейчас еще раз вернемся к NestFactory, а именно к его методу create.

public async create(module: Module) { 
        const applicationConfig = new ApplicationConfig(); 
        const container = new NestContainer(); 
        await this.initialize(module, container); 
  
        const httpServer = new ExpressAdapter() 
        container.setHttpAdapter(httpServer); 
        const instance = new NestApplication( 
            container, 
            httpServer, 
            applicationConfig, 
        ); 
        
        return instance;

У нас здесь есть класс ExpressAdapter, который наследуется от класса AbstractHttpAdapter.
То есть здесь используется паттерн проектирования известный как адаптер. При желании
можно создать и класс FastifyAdapter для использования fastify вместо express. Так и
сделано в nest, но здесь мы возьмем express из­за его большей распространенности.

Сначала рассмотрим AbstractHttpAdapter.

./core/adapters/http­adapter.ts

import { HttpServer } from "../../common/interfaces/http‐server.interface"; 
  
export abstract class AbstractHttpAdapter< 
  TServer = any, 
  TRequest = any, 
  TResponse = any 
> implements HttpServer<TRequest, TResponse> { 
  protected httpServer: TServer | undefined; 
  
  constructor(protected readonly instance: any) {} 
  
  public use(...args: any[]) { 
    return this.instance.use(...args); 
  } 
  
  public get(...args: any[]) { 
    return this.instance.get(...args); 
  } 
  
  public post(...args: any[]) { 
    return this.instance.post(...args); 
  } 
  
  public listen(port: any) { 
    return this.instance.listen(port); 
  } 
  
  public getHttpServer(): TServer { 
    return this.httpServer as TServer; 
  } 
  
  public setHttpServer(httpServer: TServer) { 
    this.httpServer = httpServer;
  } 
  
  public getInstance<T = any>(): T { 
    return this.instance as T; 
  } 
  
  abstract initHttpServer(): any;
  abstract reply(response: any, body: any, statusCode?: number): any; 
  abstract registerBodyParser(prefix?: string): any; 

Видим, что он реализует несколько обычных методов http сервера. Для упрощения кода, у
нашего nest будет только два http метода, а именно post и get.

А это интерфейс, который реализует адаптер

interface HttpServer<TRequest = any, TResponse = any> { 
    reply(response: any, body: any, statusCode?: number): any; 
    get(handler: RequestHandler<TRequest, TResponse>): any; 
    get(path: string, handler: RequestHandler<TRequest, TResponse>): any; 
    post(handler: RequestHandler<TRequest, TResponse>): any; 
    post(path: string, handler: RequestHandler<TRequest, TResponse>): any; 
    listen(port: number | string): any; 
  
    getInstance(): any; 
    getHttpServer(): any; 
    initHttpServer(): void; 
    registerBodyParser(): void 

Теперь посмотрим на класс ExpressAdapter

./platform­express/express.adapter.ts

import { AbstractHttpAdapter } from '../core/adapters'; 
import { isNil, isObject } from '../common/utils/shared.utils' 
import express from 'express'; 
import * as http from 'http'; 
import { 
  json as bodyParserJson, 
  urlencoded as bodyParserUrlencoded, 
} from 'body‐parser'; 
  
export class ExpressAdapter extends AbstractHttpAdapter { 
  
    constructor() { 
      super(express()); 
    } 
  
    /** 
     * Является response методом. С помощью него отправляются все данные. 
     */ 
    public reply(response: any, body: any) { 
        if (isNil(body)) { 
          return response.send();
        } 
    
        return isObject(body) ? response.json(body) : response.send(String(body)); 
    } 
  
    /** 
     * Запускает сервер на выборном порте 
     */ 
    public listen(port: any) { 
        return this.httpServer.listen(port); 
    } 
  
  
    public registerBodyParser() {
      const parserMiddleware = { 
        jsonParser: bodyParserJson(), 
        urlencodedParser: bodyParserUrlencoded({ extended: true }), 
      }; 
      Object.keys(parserMiddleware) 
        .forEach((parserKey: any) => this.use((parserMiddleware as any)[parserKey])); 
      } 
  
    public initHttpServer() { 
        this.httpServer = http.createServer(this.getInstance()); 
    } 

Собственно здесь реализуется запуск и настройка express. В конструкторе, в методе super,
экземпляр express передается в AbstractHttpAdapter, из которого и будут вызываться
методы post, get и use.

Теперь, снова возвращаясь к NestFactory,

public async create(module: Module) { 
        const container = new NestContainer(); 
        await this.initialize(module, container); 
  
        const httpServer = new ExpressAdapter() 
        container.setHttpAdapter(httpServer); 
        const instance = new NestApplication( 
            container, 
            httpServer, 
        ); 
        
        return instance; 
    } 

нам нужно реализовать класс NestApplication, который является экземпляром всего
приложения Nest. Именно из него вызывается метод listen,

async function bootstrap() { 
    const app = await NestFactory.create(AppModule); 
    await app.listen(3000); 

который запускает приложение.

./core/nest­application.ts

import { HttpServer } from '../common/interfaces/http‐server.interface'; 
import { Resolver } from './router/interfaces/resolver.interface'; 
import { addLeadingSlash } from '../common/utils/shared.utils'; 
import { NestContainer } from './injector/container'; 
import { RoutesResolver } from './router/routes‐resolver'; 
  
export class NestApplication { 
    private readonly routesResolver: Resolver; 
    public httpServer: any; 
  
    constructor( 
        private readonly container: NestContainer, 
        private readonly httpAdapter: HttpServer, 
    ) { 
        this.registerHttpServer(); 
  
        this.routesResolver = new RoutesResolver( 
            this.container, 
        ); 
    } 
  
    public registerHttpServer() {
        this.httpServer = this.createServer(); 
    } 
  
    /** 
     * Начинает процесс инициализации выбранного http сервера 
     */ 
    public createServer<T = any>(): T { 
        this.httpAdapter.initHttpServer(); 
        return this.httpAdapter.getHttpServer() as T; 
    } 
  
    public async init(): Promise<this> { 
        this.httpAdapter.registerBodyParser(); 
        await this.registerRouter(); 
        return this; 
    } 
  
    /** 
     * Метод, с помощью которого запускается приложение Nest. 
     * Он запускает процесс инициализации http сервера, регистрации 
     * созданных роутеров, и запуска сервера на выбранном порте. 
     */ 
    public async listen(port: number | string) { 
        await this.init(); 
        this.httpAdapter.listen(port); 
        return this.httpServer; 
    } 
  
    /** 
     * Метод, который запускает регистрацию роутеров, 
     * которые были созданы с помощью декораторов http методов, 
     * таких как post и get. 
     */ 
    public async registerRouter() { 
        const prefix = '' 
        const basePath = addLeadingSlash(prefix); 
        this.routesResolver.resolve(this.httpAdapter, basePath); 
    } 

И это подводит нас к роутерам, а именно к классу RoutesResolver.

./core/router/routes­resolver.ts

import { NestContainer } from '../injector/container'; 
import { Resolver } from '../router/interfaces/resolver.interface'; 
import { MODULE_PATH } from '../../common/constants'; 
import { HttpServer } from '../../common/interfaces/http‐server.interface'; 
import { InstanceWrapper } from '../injector/instance‐wrapper'; 
import { RouterExplorer } from './router‐explorer'; 
  
export class RoutesResolver implements Resolver { 
    private readonly routerExplorer: RouterExplorer; 
  
    constructor( 
        private readonly container: NestContainer, 
      ) { 
        this.routerExplorer = new RouterExplorer( 
            this.container, 
        ); 
    } 
  
    /** 
     * Для каждого модуля сначала находит базовый путь, который 
     * указывается в декораторе Module, 
     * и передает его и контроллеры в метод registerRouters 
     */ 
    public resolve(applicationRef: any, basePath: string): void { 
        const modules = this.container.getModules(); 
        modules.forEach(({ controllers, metatype }) => { 
            let path = metatype ? this.getModulePathMetadata(metatype) : undefined; 
            path = path ? basePath + path : basePath; 
            this.registerRouters(controllers, metatype.name, path, applicationRef); 
        }); 
    } 
  
    /** 
     * Для каждого контроллера в модуле, запускает метод explore 
     * класса routerExplorer, который отвечает за всю логику 
     * регистрации роутеров 
     */ 
    public registerRouters( 
        routes: Map<string, InstanceWrapper<any>>, 
        moduleName: string, 
        basePath: string, 
        applicationRef: HttpServer, 
      ) { 
        routes.forEach(instanceWrapper => { 
          const { metatype } = instanceWrapper; 
    
          // Находит путь для декоратора контроллера, например @Controller('cats') 
          const paths = this.routerExplorer.extractRouterPath( 
            metatype as any, 
            basePath, 
          ); 
    
          // Если путь был передан как @Controllers('cats'), то будет вызвано один раз. 
          // Дело в том, что reflect возвращает массив 
          paths.forEach(path => {
            this.routerExplorer.explore( 
              instanceWrapper, 
              moduleName, 
              applicationRef, 
              path, 
            ); 
          }); 
        }); 
      } 
  
    private getModulePathMetadata(metatype: object): string | undefined { 
        return Reflect.getMetadata(MODULE_PATH, metatype); 
    } 

Код выше делает так, чтобы для каждого контроллера был вызван метод explore класса
RouterExplorer. Класс RouterExplorer реализует основную логику регистрации роутеров. Он
создает http методы, добавляет контроллеры в качестве их callbacks, привязывает эти
контроллеры к пространству модуля, в котором он находится, и реализует
функциональность ответов и их обработки для запросов.

./core/router/routes­explorer.ts

import { NestContainer } from '../injector/container'; 
import { RouterProxyCallback } from './router‐proxy'; 
import { addLeadingSlash } from '../../common/utils/shared.utils'; 
import { Type } from '../../common/interfaces/type.interface'; 
import { Controller } from '../../common/interfaces/controller.interface'; 
import { PATH_METADATA, METHOD_METADATA, ROUTE_ARGS_METADATA, PARAMTYPES_METADATA } from '../
import { RequestMethod } from '../../common/enums/request‐method.enum'; 
import { HttpServer } from '../../common/interfaces/http‐server.interface'; 
import { InstanceWrapper } from '../injector/instance‐wrapper'; 
import { RouterMethodFactory } from '../helpers/router‐method‐factory'; 
import { 
  isConstructor, 
  isFunction, 
  isString, 
} from '../../common/utils/shared.utils'; 
import { RouteParamtypes } from '../../common/enums/route‐paramtypes.enum'; 
  
  
export interface RoutePathProperties { 
    path: string[]; 
    requestMethod: RequestMethod;
    targetCallback: RouterProxyCallback; 
    methodName: string; 
  } 
  
export class RouterExplorer { 
    private readonly routerMethodFactory = new RouterMethodFactory(); 
  
    constructor ( 
        private readonly container: NestContainer, 
    ) { 
    }     
  
    public explore<T extends HttpServer = any>( 
        instanceWrapper: InstanceWrapper, 
        module: string, 
        router: T, 
        basePath: string, 
      ) { 
        const { instance } = instanceWrapper; 
        const routePaths: RoutePathProperties[] = this.scanForPaths(instance); 
        
        // Для каждого метода контроллера запускает регистрацию роутеров 
        (routePaths || []).forEach((pathProperties: any) => { 
            this.applyCallbackToRouter( 
              router, 
              pathProperties, 
              instanceWrapper, 
              module, 
              basePath, 
            ); 
        }) 
    } 
  
    /** 
     * Метод, который сканирует контроллер, и находит у него методы 
     * запроса с определенными путями, например метод, на который 
     * навешен декоратор @post('add_to_database'). 
     * В таком случае эта функция возвращает массив методов контроллера 
     * с путями, телами этих методов, методом request и именами, которые 
     * получаются в методе exploreMethodMetadata 
     */ 
    public scanForPaths( 
        instance: Controller, 
      ): RoutePathProperties[] { 
        const instancePrototype = Object.getPrototypeOf(instance); 
        let methodNames = Object.getOwnPropertyNames(instancePrototype); 
  
        const isMethod = (prop: string) => { 
          const descriptor = Object.getOwnPropertyDescriptor(instancePrototype, prop); 
          if (descriptor?.set || descriptor?.get) { 
            return false; 
          } 
          return !isConstructor(prop) && isFunction(instancePrototype[prop]); 
        }; 
    
        return methodNames.filter(isMethod).map(method => this.exploreMethodMetadata(instance
    } 
  
    /** 
     * Для определенного метода контроллера возвращает его свойства, 
     * для метода scanForPaths 
     */ 
    public exploreMethodMetadata(
      instance: Controller, 
      prototype: object, 
      methodName: string, 
    ): RoutePathProperties { 
      const instanceCallback = (instance as any)[methodName]; 
      const prototypeCallback = (prototype as any)[methodName]; 
      const routePath = Reflect.getMetadata(PATH_METADATA, prototypeCallback); 
  
      const requestMethod: RequestMethod = Reflect.getMetadata( 
        METHOD_METADATA, 
        prototypeCallback, 
      ); 
      const path = isString(routePath) 
        ? [addLeadingSlash(routePath)] 
        : routePath.map((p: string) => addLeadingSlash(p)); 
      return { 
        path, 
        requestMethod, 
        targetCallback: instanceCallback, 
        methodName, 
      }; 
    } 
  
    private applyCallbackToRouter<T extends HttpServer>( 
        router: T, 
        pathProperties: RoutePathProperties, 
        instanceWrapper: InstanceWrapper, 
        moduleKey: string, 
        basePath: string, 
      ) { 
        const { 
          path: paths, 
          requestMethod, 
          targetCallback, 
          methodName, 
        } = pathProperties; 
        const { instance } = instanceWrapper; 
        // Получает определенный http метод 
        const routerMethod = this.routerMethodFactory 
          .get(router, requestMethod) 
          .bind(router); 
    
        // Создает callback для определенного метода 
        const handler = this.createCallbackProxy( 
          instance, 
          targetCallback, 
          methodName, 
        ); 
    
        // Если декоратор используется как @Post('add_to_database'), 
        // то будет вызвано один раз для этого пути. 
        paths.forEach(path => { 
          const fullPath = this.stripEndSlash(basePath) + path; 
          // Региструет http метод. Сопоставляет путь метода, и его callback, 
          // полученный из контроллера. Ответ же производится reply методом, 
          // реализованным в классе ExpressAdapter 
          routerMethod(this.stripEndSlash(fullPath) || '/', handler); 
        }); 
    } 
  
    public stripEndSlash(str: string) { 
      return str[str.length ‐ 1] === '/' ? str.slice(0, str.length ‐ 1) : str; 
    } 
  
    public createCallbackProxy( 
      instance: Controller, 
      callback: (...args: any[]) => unknown, 
      methodName: string, 
    ) { 
      // Достает ключи данных запроса указанных ранее в декораторах @Body() и @Param() 
      const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, instance.constructor, methodN
      const keys = Object.keys(metadata); 
      const argsLength = Math.max(...keys.map(key => metadata[key].index)) + 1 
      
      // Извлеченные данные из request, такие как тело и параметры запроса. 
      const paramsOptions = this.exchangeKeysForValues(keys, metadata); 
  
      const fnApplyParams = this.resolveParamsOptions(paramsOptions) 
      const handler = <TRequest, TResponse>( 
        args: any[], 
        req: TRequest, 
        res: TResponse, 
        next: Function, 
      ) => async () => { 
        // так как args это объект, а не примитивная переменная, 
        // то он передается по ссылке, а не по значению, 
        // поэтому он изменяется, и после вызова fnApplyParams, 
        // в args хранятся аргументы, полученные из request 
        fnApplyParams && (await fnApplyParams(args, req, res, next)); 
        // Здесь мы привязываем один из методов контроллера, 
        // например, добавление данных в базу данных, и аргументы из request, 
        // и теперь он может ими управлять, как и задумано 
        return callback.apply(instance, args); 
      }; 
      const targetCallback = async <TRequest, TResponse>( 
          req: TRequest, 
          res: TResponse, 
          next: Function, 
        ) => { 
          // Заполняется undefined для дальнейшего изменения реальными данными 
          // из request 
          const args = Array.apply(null, { argsLength } as any).fill(undefined); 
          // result это экземпляр контроллера с пространством данных аргументов 
          // из request 
          const result = await handler(args, req, res, next)() 
          const applicationRef = this.container.getHttpAdapterRef() 
          if(!applicationRef) { 
            throw new Error(`Http server not created`) 
          } 
          return await applicationRef.reply(res, result); 
        } 
      return async <TRequest, TResponse>( 
        req: TRequest, 
        res: TResponse, 
        next: () => void, 
      ) => { 
        try { 
          await targetCallback(req, res, next); 
        } catch (e) { 
          throw e 
        } 
      }; 
    } 
  
    /** 
     * extractValue здесь это метод exchangeKeyForValue. 
     * И ему передается request, для извлечения данных запроса 
     */ 
    public resolveParamsOptions(paramsOptions: any) { 
      const resolveFn = async (args: any, req: any, res: any, next: any) => { 
        const resolveParamValue = async (param: any) => { 
          const { index, extractValue } = param; 
          const value = extractValue(req, res, next); 
          args[index] = value 
        } 
        await Promise.all(paramsOptions.map(resolveParamValue)); 
      } 
      return paramsOptions && paramsOptions.length ? resolveFn : null; 
    } 
  
    /** 
     * Перебирает ключи данных запроса для вызова для каждого 
     * метода exchangeKeyForValue, который достанет соответствующие данные, 
     * которые были определены ранее в декораторах @Body() и @Param(), 
     * из request. 
     */ 
    public exchangeKeysForValues(
      keys: string[], 
      metadata: Record<number, any>, 
    ): any[] { 
      return keys.map((key: any) => { 
        const { index, data } = metadata[key]; 
        const numericType = Number(key.split(':')[0]); 
        const extractValue = <TRequest, TResponse>( 
          req: TRequest, 
          res: TResponse, 
          next: Function, 
        ) => 
          this.exchangeKeyForValue(numericType, data, { 
            req, 
            res, 
            next, 
        }); 
        return { index, extractValue, type: numericType, data } 
      }) 
    } 
  
    /** 
     * Проверяет чему соответствует ключ данных, телу или параметрам запроса. 
     * Это определяется в соответствующих декораторах @Body() и @Param(). 
     * И теперь, когда запрос на соответствующий api выполнен, мы пытаемся 
     * достать их из request, если они были переданы. 
     */ 
    public exchangeKeyForValue< 
      TRequest extends Record<string, any> = any, 
      TResponse = any, 
      TResult = any 
    >( 
      key: RouteParamtypes | string, 
      data: string | object | any, 
      { req, res, next }: { req: TRequest; res: TResponse; next: Function }, 
    ): TResult | null { 
      switch (key) { 
        case RouteParamtypes.BODY: 
          return data && req.body ? req.body[data] : req.body; 
        case RouteParamtypes.PARAM: 
          return data ? req.params[data] : req.params; 
        default: 
          return null; 
      } 
    } 
  
    public extractRouterPath(metatype: Type<Controller>, prefix = ''): string[] { 
        let path = Reflect.getMetadata(PATH_METADATA, metatype); 
    
        if (Array.isArray(path)) { 
          path = path.map(p => prefix + addLeadingSlash(p)); 
        } else { 
          path = [prefix + addLeadingSlash(path)]; 
        } 
    
        return path.map((p: string) => addLeadingSlash(p)); 
    } 

Что к этому стоит добавить, так это то, что в методе applyCallbackToRouter для получения
http метода используется класс RouterMethodFactory, который, на самом деле, имеет всего
один метод

./core/helpers/router­method­factory.ts
import { HttpServer } from '../../common/interfaces/http‐server.interface'; 
import { RequestMethod } from '../../common/enums/request‐method.enum'; 
  
export class RouterMethodFactory { 
  public get(target: HttpServer, requestMethod: RequestMethod): Function { 
    switch (requestMethod) { 
      case RequestMethod.POST: 
        return target.post; 
      default: { 
        return target.get; 
      } 
    } 
  } 

Что ж. Если вы еще здесь, поздравляю! Мы написали все ядро нашего мини Nest
фреймворка. Теперь, все, что осталось, это написать декораторы, на которых мы и пишем
Nest приложение в качестве пользователей.

Начнем с декоратора @Module(), и сперва посмотрим на пример его использования из
документации

import { Module } from '@nestjs/common'; 
import { CatsController } from './cats.controller'; 
import { CatsService } from './cats.service'; 
  
@Module({ 
  controllers: [CatsController], 
  providers: [CatsService], 
}) 
export class CatsModule {} 

Мы видим, что метаданные указываются как параметры декоратора, теперь реализуем его.

./common/decorators/module.decorator.ts

import { MODULE_METADATA as metadataConstants } from '../constants'; 
  
const metadataKeys = [ 
  metadataConstants.IMPORTS, 
  metadataConstants.EXPORTS, 
  metadataConstants.CONTROLLERS, 
  metadataConstants.PROVIDERS, 
]; 
  
/** 
 * Проверяет, чтобы были указаны только правильные массивы, 
 * соответствующие metadataKeys 
 */ 
export function validateModuleKeys(keys: string[]) { 
  const validateKey = (key: string) => { 
    if (metadataKeys.includes(key)) { 
      return; 
    } 
    throw new Error(`NOT INVALID KEY: ${key}`); 
  }; 
  keys.forEach(validateKey); 

  
/** 
 * Сохраняет зависимости в объект Reflect. 
 * Где property название одной из зависимости, 
 * например controllers. Именно благодаря этому, 
 * у нас есть возможность извлекать данные после. 
 */ 
export function Module(metadata: any): ClassDecorator { 
  const propsKeys = Object.keys(metadata); 
  validateModuleKeys(propsKeys); 
  
  return (target: Function) => { 
    for (const property in metadata) { 
      if (metadata.hasOwnProperty(property)) { 
        Reflect.defineMetadata(property, (metadata as any)[property], target); 
      } 
    } 
  }; 

Довольно не сложно, не так ли? Действительно, декораторы одна из довольно простых
частей Nest.

Теперь рассмотрим декоратор @Controller(), который, все, что делает, это сохраняет
базовый путь контроллера, ведь сам контроллер уже сохранен в Reflect по модулю, в
котором он используется.

./common/decorators/controller.decorator.ts

import { PATH_METADATA } from "../constants"; 
import { isUndefined } from "../utils/shared.utils"; 
  
export function Controller( 
  prefix?: string, 
): ClassDecorator { 
  const defaultPath = '/'; 
  
  const path = isUndefined(prefix) ? defaultPath : prefix 
  
  return (target: object) => { 
    Reflect.defineMetadata(PATH_METADATA, path, target); 
  }; 

Помните про декоратор @Injectable(), который якобы помечает класс как провайдер? Как
уже написано выше, он лишь устанавливает время жизни провайдера. Класс помечается
как провайдер, только если он передается в массив providers соответствующего модуля. И
хоть мы не реализовали возможность изменения времени жизни для провайдера, но для
полноты, все равно рассмотрим этот декоратор.

./common/decorators/injectable.decorator.ts

import { SCOPE_OPTIONS_METADATA } from '../constants'; 
  
export enum Scope { 
    DEFAULT, 
    TRANSIENT, 
    REQUEST, 

  
export interface ScopeOptions { 
    scope?: Scope; 

  
export type InjectableOptions = ScopeOptions; 
  
export function Injectable(options?: InjectableOptions): ClassDecorator { 
    return (target: object) => { 
      Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target); 
    }; 

Теперь у нас осталось всего четыре декоратора для реализации, для данных запроса, а
именно @Body() и @Param(), и для http методов, @Post() и @Get().

Сперва рассмотрим первые два.

./common/decorators/route­params.decorator.ts

import { ROUTE_ARGS_METADATA } from "../constants"; 
import { RouteParamtypes } from "../enums/route‐paramtypes.enum"; 
import { isNil, isString } from "../utils/shared.utils"; 
  
/** 
 * Здесь используется неизменяемость данных, для того, чтобы 
 * использовать один метод для нескольких типов запроса. 
 */ 
const createPipesRouteParamDecorator = (paramtype: RouteParamtypes) => ( 
    data?: any, 
  ): ParameterDecorator => (target, key, index) => { 
    const hasParamData = isNil(data) || isString(data); 
    const paramData = hasParamData ? data : undefined; 
    const args = 
      Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {}; 
  
    // Где paramtype это body или param, а index его 
    // положение в параметрах функции, где находится декоратор, 
    // для правильного присвоения после получения из request 
    Reflect.defineMetadata( 
      ROUTE_ARGS_METADATA, 
      { 
        ...args, 
        [`${paramtype}:${index}`]: { 
          index, 
          data: paramData, 
        }, 
      }, 
      target.constructor, 
      key, 
    ); 
}; 
  
export function Body( 
    property?: string, 
  ): ParameterDecorator { 
    return createPipesRouteParamDecorator(RouteParamtypes.BODY)( 
      property, 
    ); 

  
export function Param( 
    property?: string, 
  ): ParameterDecorator { 
    return createPipesRouteParamDecorator(RouteParamtypes.PARAM)( 
      property, 
    ); 

И последнее, декораторы post и get, которые сохраняют в объект Reflect для определенных
методов контроллеров их пути и методы запроса.

./common/decorators/request­mapping.decorator.ts

import { METHOD_METADATA, PATH_METADATA } from '../constants'; 
import { RequestMethod } from '../enums/request‐method.enum'; 
  
export interface RequestMappingMetadata { 
  path?: string | string[]; 
  method?: RequestMethod; 

  
const defaultMetadata = { 
  [PATH_METADATA]: '/', 
  [METHOD_METADATA]: RequestMethod.GET, 
}; 
  
export const RequestMapping = ( 
  metadata: RequestMappingMetadata = defaultMetadata, 
): MethodDecorator => { 
  const pathMetadata = metadata[PATH_METADATA]; 
  const path = pathMetadata && pathMetadata.length ? pathMetadata : '/'; 
  const requestMethod = metadata[METHOD_METADATA] || RequestMethod.GET; 
  
  return ( 
    target: object, 
    key: string | symbol, 
    descriptor: TypedPropertyDescriptor<any>, 
  ) => { 
    Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); 
    Reflect.defineMetadata(METHOD_METADATA, requestMethod, descriptor.value); 
    return descriptor; 
  }; 
}; 
  
const createMappingDecorator = (method: RequestMethod) => ( 
  path?: string | string[], 
): MethodDecorator => { 
  return RequestMapping({ 
    [PATH_METADATA]: path, 
    [METHOD_METADATA]: method, 
  }); 
}; 
  
/** 
 * Обработчик маршрута (метод) Decorator. Направляет запросы HTTP POST по указанному пути.
 * 
 * @publicApi 
 */ 
export const Post = createMappingDecorator(RequestMethod.POST); 
  
/** 
 * Обработчик маршрута (метод) Decorator. Направляет запросы HTTP GET по указанному пути.
 * 
 * @publicApi 
 */ 
export const Get = createMappingDecorator(RequestMethod.GET); 

Хорошая работа, наш мини Nest готов! 

Теперь мы можем создать директорию project­view на уровне других директорий nest, и
написать простое приложение
./project­view/main.ts

import { NestFactory } from '../core'; 
import { AppModule } from './app.module'; 
  
async function bootstrap() { 
  const app = await NestFactory.create(AppModule); 
  await app.listen(3000); 

bootstrap();

./project­view/app.controller.ts

import { Controller, Get, Post, Body, Param } from '../common'; 
import { AppService } from './app.service'; 
  
@Controller() 
export class AppController { 
  constructor(private readonly appService: AppService) {} 
  
  @Get() 
  getHello(): string { 
    return this.appService.getHello(); 
  } 
  
  @Post('body/:id') 
  recieveBody(@Body() data: any, @Param('id') id: string) { 
    return 'body: ' + data.data + ' has been received and id: ${id}'; 
  } 

./project­view/app.service.ts

import { Injectable } from '../common'; 
  
@Injectable() 
export class AppService { 
  getHello(): string { 
    return 'Hello World!'; 
  } 

./project­view/app.module.ts

import { Module } from '../common'; 
import { AppController } from './app.controller'; 
import { AppService } from './app.service'; 
  
@Module({ 
  imports: [], 
  controllers: [AppController], 
  providers: [AppService], 
}) 
export class AppModule {}

После чего инициализировать typescript проект, создав tsconfig.json файл с помощью
команды

tsc ‐‐init

и настроить его как­то вот так


  "compilerOptions": { 
    "target": "es2017", 
    "experimentalDecorators": true, 
    "emitDecoratorMetadata": true, 
    "module": "commonjs", 
    "outDir": "./", 
    "esModuleInterop": true, 
    "forceConsistentCasingInFileNames": true, 
    "strict": true, 
    "skipLibCheck": true, 
  }, 
  "include": ["packages/**/*", "integration/**/*", "./core/", "./common/", "./project‐view/",
  "exclude": ["node_modules", "**/*.spec.ts"] 

Теперь мы можем скомпилировать typescript в js с помощью следующей команды

tsc ‐‐build

перейти в директорию нашего пользовательского приложения

cd project‐view

и запустить скомпилированный входной файл

node main.js

Вы можете проверить результат, например, через postman, и поиграться с body и params
для post запроса.

Теперь вы знаете Nest чуть лучше :)

Гитхаб репозиторий со всем кодом

Контакты для связи: почта — keith.la.00@gmail.com, telegram — @NLavrenov00

Теги:  nest, typescript, исходный код

Хабы:  JavaScript, Node.JS, TypeScript

+8 14K 89 0

10 11
Карма Рейтинг

Никита Лавренов @Keithla
Backend разработчик

Комментировать

Публикации

ЛУЧШИЕ ЗА СУТКИ ПОХОЖИЕ


 ꞏ   ꞏ 

 ꞏ   ꞏ 

 ꞏ   ꞏ 

 ꞏ   ꞏ 

 ꞏ   ꞏ 

ИСТОРИИ
РАБОТА

JavaScript разработчик
370 вакансий

React разработчик
156 вакансий

Node.js разработчик
95 вакансий

Все вакансии

Ваш аккаунт Разделы Информация Услуги


Ваш аккаунт Разделы Информация Услуги

Войти Публикации Устройство сайта Корпоративный блог

Регистрация Новости Для авторов Медийная реклама

Хабы Для компаний Нативные проекты

Компании Документы Образовательные

Авторы Соглашение программы

Песочница Конфиденциальность Стартапам

Мегапроекты

Настройка языка

Техническая поддержка

Вернуться на старую версию

© 2006–2023, Habr

Вам также может понравиться