- Что такое NestJS?
- Предварительные условия
- Общие терминологии, используемые в NestJS;
- Интерфейсы
- Контроллеры
- Сервисы
- Декораторы
- Модуль
- Установка CLI
- Создание нового проекта
- Переменные среды
- Генерация модулей
- Генерация интерфейсов
- Генерация служб и контроллеров
- Сервис
- Контроллер
- MongoDB(Mongoose).
- Создание схемы.
- Регистрация схемы
- Инжектирование модели
- Как работает маршрутизация
- Логика сервиса и контроллера.
- Создайте
- Прочитать
- Обновление
- Удаление
- Тестирование API
- Создайте
- Прочитать
- Обновить
- Удалить
- Заключение
Что такое 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!