Азбука NestJS: руководство для начинающих с MongoDB(Mongoose).


Что такое NestJS?

NestJS – это современный NodeJS-фреймворк, который использует под капотом популярные NodeJS-фреймворки, такие как Express и Fastify. NestJS был в значительной степени вдохновлен Angular, и, как следствие, в нем используется система модулей в стиле Angular. NestJS написан на TypeScript, хотя он также поддерживает родной JavaScript.

Предварительные условия

Чтобы следовать этому руководству, вы должны соответствовать следующим требованиям

  • Знание PostMan или любого другого инструмента для тестирования API.
  • Базовые знания NodeJS и Express-приложений.
  • Базовые знания TypeScript.
  • Знание MongoDB (Mongoose).

В вашей системе должно быть установлено следующее

  • NodeJS v.14 и выше.
  • Visual Studio Code (рекомендуется) или любая другая IDE.
  • PostMan или любой другой инструмент для тестирования API.

Общие терминологии, используемые в NestJS;

Вот некоторые из наиболее часто используемых терминов в NestJS, которые вы будете часто встречать в этой статье.

Интерфейсы

Интерфейс – это определение типа. В результате он используется как средство проверки/определения типов в функциях, классах и т.д.

interface humanInterface{
  name:string;
  gender:string;
  age:number;
}

const kevin: humanInterface={
  name:'Kevin Sunders',
  gender:'Male',
  age: 25,
}
Вход в полноэкранный режим Выход из полноэкранного режима

Приведенный выше humanInterface выполняет строгую проверку типов для объекта kevin. Typescript выдаст ошибку, если вы добавите еще одно поле или измените тип любого из свойств объекта.

Контроллеры

Контроллеры отвечают за прием входящих запросов и ответы клиенту. Контроллер взаимодействует со связанным с ним сервисом.

Сервисы

Сервис – это поставщик, который хранит и извлекает данные и используется с соответствующим контроллером.

Декораторы

Декоратор – это выражение, возвращающее функцию, которое принимает target, name и property descriptor в качестве необязательных аргументов. Декораторы записываются как @decorator-name. Обычно они прикрепляются к объявлениям классов, методам и параметрам.

@Get()
   getAll(): Model[] {
    return this.testService.getAll();
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Декоратор @Get выше отмечает блок кода под ним как запрос GET. Подробнее об этом позже.

Модуль

Модуль – это часть программы, которая выполняет определенную задачу. Модуль в NestJS помечается путем аннотирования класса декоратором @Module(). Nest использует метаданные, предоставляемые декоратором @Module(), для организации структуры приложения.

Установка CLI

Для начала работы вам необходимо установить NestJS CLI **** с помощью npm. Вы можете пропустить этот шаг, если NestJS CLI уже установлен в вашей системе.

npm i -g @nestjs/cli
Вход в полноэкранный режим Выход из полноэкранного режима

Этот блок кода выше установит nest CLI глобально на вашу систему.

Создание нового проекта

Чтобы создать новый проект, выполните команду nest new, за которой последует желаемое имя проекта. В этой статье мы будем писать простой API для блога с функцией CRUD, придерживаясь при этом стандартов RESTful.

nest new Blog-Api
Вход в полноэкранный режим Выйти из полноэкранного режима

Эта команда предложит вам выбрать менеджер пакетов, выберите npm.

После этого вся структура проекта будет сконфигурирована с тестовой конечной точкой API, порт которой по умолчанию установлен на 3000. Вы можете протестировать его по адресу http://localhost:3000 после выполнения команды npm run start:dev, которая запустит сервер в режиме наблюдения, подобно тому, как это делает nodemon в экспресс-приложениях.

После тестирования конечной точки вам нужно будет удалить некоторые файлы по умолчанию, поскольку они вам больше не понадобятся. Для этого;

  • откройте папку src и внутри нее
  • удалите app.controller.spec.ts,
  • удалите app.controller.ts,
  • удалите app.service.ts,
  • Откройте app.module.ts,
  • Удалите ссылку на AppController в массиве controllers и импорте,
  • Удалите ссылку на AppService в массиве providers и импортах.

Вам также может понадобиться изменить README.md в соответствии с вашими спецификациями.

Ваш файл app.module.ts должен выглядеть следующим образом,

//app.module.ts

import { Module } from '@nestjs/common';

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {}
Вход в полноэкранный режим Выход из полноэкранного режима

Переменные среды

В качестве хорошей практики, некоторая конфиденциальная информация в вашем коде не должна быть общедоступной. Например, ваш PORT и ваш MongoDB URI.

Давайте исправим это в вашем коде.

В терминале запустите

npm i dotenv
Войти в полноэкранный режим Выйдите из полноэкранного режима

Затем создайте файл .env в вашей директории и добавьте его в ваш файл .gitignore. Сохраните вашу переменную PORT, позже вам также придется сохранить ваш MongoDB URI в том же месте. Теперь замените открытую переменную PORT в файле main.ts. Для этого импортируйте пакет dotenv и вызовите метод .config().

import * as dotenv from 'dotenv';
dotenv.config();
Вход в полноэкранный режим Выйти из полноэкранного режима

Это должен быть ваш файл main.ts после того, как вы выполните описанные выше шаги.

//main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as dotenv from 'dotenv';
dotenv.config();

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT);
}
bootstrap();
Вход в полноэкранный режим Выход из полноэкранного режима

Генерация модулей

Чтобы сгенерировать модуль NestJS с помощью NestJS CLI, выполните приведенный ниже фрагмент кода.

nest generate module blogs
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда создает папку blogs, которая содержит файл blogs.module.ts и регистрирует BlogsModule в вашем файле app.module.ts.

Генерация интерфейсов

Давайте сгенерируем интерфейс с помощью NestJS CLI для проверки типа объекта, который будет представлять записи вашего блога. Для этого сначала нужно cd в папку blogs, поскольку рекомендуется хранить их рядом с объектами домена, с которыми они связаны.

cd src/blogs
Вход в полноэкранный режим Выйдите из полноэкранного режима

Затем выполните приведенный ниже фрагмент кода, чтобы сгенерировать интерфейс.

nest generate interface blogs
Вход в полноэкранный режим Выйти из полноэкранного режима

Это создаст файл blogs.interface.ts. Здесь мы определим наш интерфейс. Мы назовем интерфейс BlogsInterface.

export interface BlogsInterface {
  title: string;
  body: string;
  category: string;
  dateCreated: Date;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Прежде чем выполнять какие-либо команды на терминале, не забудьте cd выйти из папки src и вернуться в корневую папку, выполнив команду

cd ../..
Войти в полноэкранный режим Выйти из полноэкранного режима

Генерация служб и контроллеров

Вам потребуется создать класс сервиса для хранения и получения данных и обработки всей логики и класс контроллера для обработки всех входящих запросов и исходящих ответов.

Сервис

Чтобы создать сервис, выполните приведенную ниже команду,

nest generate service blogs
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда создает два файла blogs.service.spec.ts и blogs.service.ts и регистрирует сервис в массиве providers в blogs.module.ts.

Контроллер

Чтобы сгенерировать контроллер, выполните приведенную ниже команду,

nest generate controller blogs
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда создает два файла blogs.controller.spec.ts и blogs.controller.ts и регистрирует контроллер в массиве controllers в blogs.module.ts.

С этим ваша структура блогов почти завершена, вам осталось сделать BlogsService доступным для других частей вашей программы. Вы можете сделать это, создав массив exports в файле blogs.module.ts и зарегистрировав BlogsService в этом массиве.

//blogs.module.ts

import { Module } from '@nestjs/common';
import { BlogsService } from './blogs.service';
import { BlogsController } from './blogs.controller';

@Module({
  providers: [BlogsService],
  controllers: [BlogsController],
  exports: [BlogsService],
})
export class BlogsModule {}
Вход в полноэкранный режим Выход из полноэкранного режима

MongoDB(Mongoose).

Установите mongoose, выполнив команду ,

npm install --save @nestjs/mongoose mongoose
Войти в полноэкранный режим Выйдите из полноэкранного режима

После установки импортируйте {MongooseModule} из '@nestjs/mongoose' в ваш файл app.module.ts. Затем возьмите ваш MongoDB URI и сохраните его в файле .env. Повторите шаги для импорта dotenv в файле app.module.ts. Затем в массиве imports вызовите метод .forRoot(), который принимает ваш MongoDB URI в качестве аргумента на MongooseModule. Аналогично mongoose.connect() в обычных экспресс-приложениях.

@Module({
  imports: [BlogsModule, MongooseModule.forRoot(process.env.MONGODB_URI)],
Вход в полноэкранный режим Выход из полноэкранного режима

Создание схемы.

Давайте создадим схему для определения формы блогов в нашей коллекции. Для этого,

  • Создайте папку внутри папки blogs, назовите ее schemas,
  • Внутри папки schemas создайте файл и назовите его blogs.schema.ts.

Затем,

Во-первых, вам необходимо,

  • Импортировать декоратор prop, декоратор Schema и SchemaFactory из @nestjs/mongoose,
  • Создайте класс Blog и экспортируйте его,
  • Превратите класс в схему, поместив декоратор @Schema() над классом,
  • Создайте константу BlogSchema, назначьте возвращаемое значение вызова .createForClass(Blog) с именем вашего класса в качестве аргумента на SchemaFactory, которую вы импортировали ранее.
//blogs.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

@Schema()
export class Blog {}

export const BlogSchema = SchemaFactory.createForClass(Blog);
Вход в полноэкранный режим Выход из полноэкранного режима

Затем вам нужно будет определить свойства схемы.

Чтобы определить свойство в схеме, нужно отметить каждое из них декоратором @prop(). Декоратор @prop принимает объект options или объявление сложного типа. Объявления сложных типов могут быть массивами и вложенными объявлениями типов объектов.

//blogs.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

@Schema()
export class Blog {
  @Prop({ required: true })
  title: string;

  @Prop({ required: true })
  body: string;

  @Prop({ required: true })
  category: string;

  @Prop({ required: true })
  dateCreated: Date;
}

export const BlogSchema = SchemaFactory.createForClass(Blog);
Вход в полноэкранный режим Выход из полноэкранного режима

Далее import { Document } from 'mongoose'.

Затем создайте тип объединения с классом Schema и импортированным Document. Например,

//blogs.schema.ts

import { Document } from 'mongoose';

export type BlogDocument = Blog & Document;
Войти в полноэкранный режим Выйти из полноэкранного режима

Ваш окончательный файл blogs.schema.ts должен выглядеть следующим образом,

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type BlogDocument = Blog & Document;

@Schema()
export class Blog {
  @Prop({ required: true })
  title: string;

  @Prop({ required: true })
  body: string;

  @Prop({ required: true })
  category: string;

  @Prop({ required: true })
  dateCreated: Date;
}

export const BlogSchema = SchemaFactory.createForClass(Blog);
Войти в полноэкранный режим Выход из полноэкранного режима

Регистрация схемы

Вам нужно импортировать все в ваш файл blogs.module.ts. Для этого вам нужно,

  • Импортировать {MongooseModule} из '@nestjs/mongoose',
  • Импортировать {Blog, BlogSchema} из './schemas/blogs.schema'.
  • Создайте массив imports внутри декоратора @module.
  • Вызовите метод .forFeature() на MongooseModule. Он принимает массив, содержащий объект, определяющий name и свойство schema, которые должны быть установлены в Blog.name и BlogSchema соответственно.
@Module({
  imports: [
    MongooseModule.forFeature([{ name: Blog.name, schema: BlogSchema }]),
  ],
Вход в полноэкранный режим Выход из полноэкранного режима

Инжектирование модели

Вам необходимо внедрить модель Blog в blogs.service.ts с помощью декоратора @InjectModel(). Для этого вам необходимо

  • import { Model } from 'mongoose',
  • импортировать { InjectModel } из '@nestjs/mongoose',
  • Импортируйте {Blog, BlogDocument} из './schemas/blogs.schema',
  • Создайте конструктор внутри класса BlogsService,
  • Объявите переменную private, назовите ее blogModel и присвойте ей тип Model<BlogDocument>. Все методы mongoose будут вызываться на этой переменной.

Напомним, что BlogDocument – это тип объединения класса Blog и Mongoose Model, который вы создали ранее. Он используется в качестве общего типа для вашей переменной.

  • Украсьте blogModel командой @InjectModel() и передайте Blog.name в качестве аргумента.
constructor(
    @InjectModel(Blog.name)
    private blogModel: Model<BlogDocument>,
  ) {}
Вход в полноэкранный режим Выход из полноэкранного режима

Как работает маршрутизация

Вы, наверное, уже заметили, что в декоратор @Controller передается строка 'blogs'. Это означает, что контроллер будет отправлять все ответы и обрабатывать все запросы, сделанные на http://localhost/3000/blogs.

Далее вы реализуете логику сервиса и контроллера.

Логика сервиса и контроллера.

Наконец-то пришло время реализовать функциональность CRUD.

Прежде чем мы начнем, вам нужно будет настроить контроллер. Начните с импорта некоторых декораторов методов HTTP в ваш контроллер.

//blogs.controller.ts

import {
  Controller,
  Body,
  Delete,
  Get,
  Post,
  Put,
  Param,
} from '@nestjs/common';
Вход в полноэкранный режим Выход из полноэкранного режима

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

//blogs.controller.ts

import { BlogsInterface } from './blogs.interface';
import { BlogsService } from './blogs.service';
Вход в полноэкранный режим Выход из полноэкранного режима

Для регистрации сервиса создайте constructor внутри класса BlogsController и объявите private readonly переменную service и установите ее тип BlogsService.

constructor(private readonly service: BlogsService) {}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда все готово, давайте начнем.

Создайте

Логика сервиса

Импортируйте { BlogsInterface } из './blogs. interface' и добавьте функцию async в класс BlogsService под названием createBlog, которая будет принимать один параметр blog, с типом BlogInterface и типом возврата Promise с общим типом <Blog>.

async createBlog(blog: BlogsInterface): Promise<Blog> {
    return await new this.blogModel({
      ...blog,
      dateCreated: new Date(),
    }).save();
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Логика контроллера

В класс BlogsController добавьте функцию async. Назовите ее createBlog и пометьте ее декоратором @Post, который определяет ее как POST запрос.createBlog принимает один параметр blog, с типом BlogInterface. Пометьте параметр декоратором @Body, который извлекает весь объект body из объекта req и заполняет декорированный параметр значением body.

@Post()
  async createBlog(
    @Body()
    blog: BlogsInterface,
  ) {
    return await this.service.createBlog(blog);
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Прочитать

Добавьте два метода async, один для возврата одного сообщения блога, а второй для возврата всех сообщений блога.

Логика сервиса

async getAllBlogs(): Promise<Blog[]> {
    return await this.blogModel.find().exec();
  }

  async getBlog(id: string): Promise<Blog> {
    return await this.blogModel.findById(id);
  }
Вход в полноэкранный режим Выйти из полноэкранного режима

Логика контроллера

  @Get()
  async getAllBlogs() {
    return await this.service.getAllBlogs();
  }

  @Get(':id')
  async getBlog(@Param('id') id: string) {
    return await this.service.getBlog(id);
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Функции async помечены декоратором @Get, который определяет их как запрос GET.

Декоратор второй функции async имеет аргумент ':id'. Это то, что вы передадите в декоратор @Param. Параметр обозначается командой @Param('id'), которая извлекает свойство params из объекта req и заполняет декорированный параметр значением params.

Обновление

Давайте реализуем логику для запроса PUT.

Логика работы сервиса

async updateBlog(id: string, body: BlogsInterface): Promise<Blog> {
    return await this.blogModel.findByIdAndUpdate(id, body);
  }
Войти в полноэкранный режим Выйти из полноэкранного режима

Логика контроллера

@Put(':id')
  async updateBlog(
    @Param('id')
    id: string,
    @Body()
    blog: BlogsInterface,
  ) {
    return await this.service.updateBlog(id, blog);
  } 
Вход в полноэкранный режим Выход из полноэкранного режима

Второй параметр функции async помечен декоратором @Body(), который извлекает весь объект body из объекта req и заполняет декорированный параметр значением body.

Удаление

Давайте реализуем логику для запросов delete.

Логика работы сервиса

async deleteBlog(id: string): Promise<void> {
    return await this.blogModel.findByIdAndDelete(id);
  }
Вход в полноэкранный режим Выйти из полноэкранного режима

Общий тип Promise является void, потому что запрос Delete возвращает пустое обещание.

Логика контроллера

@Delete(':id')
  async deleteBlog(@Param('id') id: string) {
    return await this.service.deleteBlog(id);
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Тестирование API

Чтобы протестировать этот API, необходимо использовать инструмент тестирования API. В этой статье я буду использовать популярный инструмент тестирования API под названием Postman. Для тестирования я буду использовать случайные данные о популярных темах.

Создайте

Сделайте запрос POST на http://localhost/3000/blogs со следующими объектами JSON, это добавит все данные в вашу базу данных.

{
  "title": "jeen-yuhs",
  "body": "The life of superstar rapper Kanye West is currently streaming on Netflix - and according to our jeen-yuhs review, it's a fascinating watch. -credit:Radio Times",
  "category":"Music"
}

Войдите в полноэкранный режим Выход из полноэкранного режима
{
  "title": "Why You Should Always Wash Your Hands",
  "body": "Germs from unwashed hands can be transferred to other objects, like handrails, tabletops, or toys, and then transferred to another person's hands.-credit cdc.gov",
  "category":"Health"
}

Войти в полноэкранный режим Выход из полноэкранного режима
{
  "title": "Why You Should Follow me on Twitter",
  "body": "Well, Because I asked nicely",
  "category":"Random"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы должны получить ответ 201 и созданный блог с датой и добавлением _id.

Прочитать

Сделайте запрос GET к http://localhost/3000/blogs. Это должно вернуть

200 ответ с массивом всех данных, которые вы ранее добавили. Скопируйте свойство _id одного из объектов массива.

Сделайте еще один запрос GET к http://localhost/3000/blogs/id с ранее скопированным id. В ответ должен прийти ответ 200 с данными объекта, id которого был использован для запроса.

Обновить

Сделайте запрос PUT к http://localhost/3000/blogs/id с приведенными ниже данными. id должен быть заменен на тот, который вы скопировали ранее. Это должно вернуть ответ 200 и обновить объект с id за кулисами. Если вы выполните другой запрос GET, вы получите обновленный объект.

{
  "title": "why you Should Cut your Nails",
  "body": "It's important to trim your nails regularly. Nail trimming together with manicures makes your nails look well-groomed, neat, and tidy.- credit:WebMD",
  "category":"Health"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Удалить

Выполните запрос DELETE к http://localhost/3000/blogs/id. Это должно вернуть ответ 200 и удалить объект с id за кулисами. Если вы выполните другой запрос GET, вы не увидите удаленный объект.

Заключение

Итак, мы наконец-то подошли к концу этой статьи. Давайте подытожим то, что вы изучили.

  • Что такое NestJS,
  • Терминология в NestJS,
  • Создание приложения NestJS,
  • Интеграция MongoDB в приложение NestJS,
  • Манипулирование и приложение NestJS,

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

Вы можете найти код на github.

Удачи на вашем пути NestJS!

Оцените статью
Procodings.ru
Добавить комментарий