Какво е NestJS?
NestJS е модерна NodeJS рамка, която използва популярни NodeJS рамки като Express и Fastify под капака. NestJS до голяма степен е вдъхновен от Angular и в резултат на това използва модулна система в стил Angular. NestJS е написан на TypeScript, въпреки че поддържа и собствен JavaScript.
Предпоставки
За да следвате този урок, трябва да отговаряте на следните изисквания
- Компетентност в PostMan или друг инструмент за тестване на API.
- Основни познания за приложенията NodeJS и Express.
- Основни познания за TypeScript.
- Компетентност в MongoDB(Mongoose).
Следното трябва да бъде инсталирано на вашата система
- NodeJS v.14 и по-нова версия.
- Код на Visual Studio (препоръчително) или всяка друга 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
Този кодов блок по-горе ще инсталира гнездовия 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
декораторът приема обект с опции или декларация за сложен тип. Декларациите за сложни типове могат да бъдат масиви и вложени декларации за тип обекти.
//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);
Следващ импорт { Document }
от '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 }
от'mongoose'
, - import
{ InjectModel }
от'@nestjs/mongoose’
, - Импортиране на
{Blog, BlogDocument}
от'./schemas/blogs.schema’
, - Създайте
constructor
вътре в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
request връща празно обещание.
Логика на контролера
@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
. Това трябва да върне a
200
отговор с масив от всички данни, които сте добавили по-рано. Копирайте _id
свойство на един от обектите на масива.
Направете друг GET
заявка до http://localhost/3000/blogs/id
с копирания по-рано идент. Това трябва да върне 200
отговор с данните на обекта, чийто идентификатор е използван за подаване на заявката.
Актуализация
Направете 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 пътуване!