Как добавить фильтрацию, сортировку, ограничение и нумерацию страниц в ваше приложение Nest.js
Если вы читаете эту статью, вы, вероятно, разработчик, который использовал API для вызова данных для своего приложения. Возможно, вы также использовали такие функции, как фильтрация и нумерация страниц, чтобы ограничить объем данных, которые вы хотите получить.
Эти функции API важны при создании собственного API. Они помогают гарантировать, что ваш API работает быстро, безопасно и легко понятен людям, использующим его.
В этой статье вы создадите простой API расходов с помощью Nest.js и MongoDB для базы данных. Затем вы реализуете фильтрацию, сортировку, ограничение и нумерацию страниц, чтобы сделать ваш API более быстрым и простым в использовании. Давай начнем!
Что такое API?
API — это основа современной разработки программного обеспечения. API означает интерфейс прикладного программирования и позволяет одной части программного обеспечения взаимодействовать с другой частью программного обеспечения.
Веб-API используют протоколы связи HTTP для связи с компьютерами. Существуют разные типы API, но мы не будем здесь в них углубляться.
В наши дни веб-API обычно имеют такие функции проектирования, как фильтрация, сортировка, ограничение и разбиение на страницы, чтобы сделать API быстрее, проще в использовании и более безопасным. В этой статье вы узнаете, как реализовать эти функции для улучшения API.
Как настроить приложение Nest.js
Прежде всего, вы создадите простой CRUD API для расходов в Nestjs. Nestjs — это прогрессивная платформа Node.js для создания современных API, которая довольно проста в использовании.
Для начала откройте командную строку и введите следующие команды:
$ npm i -g @nestjs/cli
$ nest new expense-app
Следуйте руководству по установке. Это создаст для вас несколько шаблонных файлов. Затем вы можете открыть приложение Nest.js в любой IDE по вашему выбору.
Как настроить MongoDB
Поскольку мы предпочитаем MongoDB, важно настроить ее так, чтобы вы могли использовать ее в своем приложении. Сначала установите пакет @nestjs/mongoose
:
$ npm i @nestjs/mongoose mongoose
После завершения установки зайдите в файл модуля приложения и импортируйте MongooseModule
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot(`mongodb+srv://*******:*******@cluster0.30vt0jd.mongodb.net/expense-test-app`)],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Если вы не хотите раскрывать свою строку таким образом, вы можете создать файл .env
и сохранить в нем строку подключения к базе данных. Затем вам нужно будет установить пакет @nestjs/config
, который внутри использует dotenv
.
Внутри файла модуля приложения импортируйте ConfigModule
:
@Module({
imports: [
ConfigModule.forRoot({ envFilePath: '.env', isGlobal: true }),
MongooseModule.forRoot(process.env.DATABASE),
],
controllers: [AppController],
providers: [AppService],
})
isGlobal: true
означает, что модуль доступен везде в вашем приложении. envFilePath
указывает путь к вашему файлу .env
.
Как настроить конечные точки расходов
Вы настроили MongoDB. Теперь пришло время настроить конечные точки расходов.
Вы можете создать модуль в Nest.js, введя в терминал следующую команду: nest generate module expenses
. Вы также можете сгенерировать контроллер следующим образом: nest generate module expenses
контроллера и услугу следующим образом: nest generate module expenses
на обслуживание. Все это следует делать в папке src
.
Продолжайте и создайте файл Costs.schema.ts
. Внутри файла Costes.schema.ts
создайте схему расходов с некоторой проверкой.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { HydratedDocument } from 'mongoose';
export type ExpenseDocument = HydratedDocument<Expense>;
@Schema()
export class Expense {
@Prop({
trim: true,
required: [true, 'Title is required'],
})
title: string;
@Prop({
min: 0,
required: [true, 'Amount is required'],
})
amount: number;
@Prop({
type: String,
trim: true,
required: [true, 'Category is required'],
})
category: string;
@Prop({
type: Date,
default: Date.now,
})
incurred: Date;
@Prop({ type: String, trim: true })
notes: string;
@Prop()
slug: string;
@Prop()
updated: Date;
@Prop({
type: Date,
default: Date.now,
})
created: Date;
}
export const ExpenseSchema = SchemaFactory.createForClass(Expense);
Свойства в этой схеме включают заголовок, сумму, категорию, примечания и ярлык.
После этого вам нужно зарегистрировать модель расходов в файле Costs.module.ts
:
import { Module } from '@nestjs/common';
import { ExpensesController } from './expenses.controller';
import { ExpensesService } from './expenses.service';
import { MongooseModule } from '@nestjs/mongoose';
import { Expense, ExpenseSchema } from './expenses.schema';
@Module({
imports: [
MongooseModule.forFeature([{ name: Expense.name, schema: ExpenseSchema }]),
],
controllers: [ExpensesController],
providers: [ExpensesService],
})
export class ExpensesModule {}
После регистрации схемы вы можете внедрить модель Expense
в ExpensesService
с помощью декоратора @InjectModel()
.
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Expense } from './expenses.schema';
import { Model } from 'mongoose';
import { ExpenseDto } from './dto/expense.dto';
@Injectable()
export class ExpensesService {
constructor(
@InjectModel(Expense.name) private expenseModel: Model<Expense>,
) {}
async createExpense(data: ExpenseDto) {
const expense = this.expenseModel.create(data);
return expense;
}
async getExpenses() {
const expenses = await this.expenseModel.find();
return expenses;
}
}
В файл ExpensesService мы внедрили модель расходов в качестве зависимости с помощью декоратора @InjectModel()
. Сделав это, вы можете определить функцию, которая создает расходы, и функцию, которая получает все расходы.
В свой контроллер добавьте следующий код:
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
import { ExpensesService } from './expenses.service';
import { ExpenseDto } from './dto/expense.dto';
@Controller('expenses')
export class ExpensesController {
constructor(private readonly expenseService: ExpensesService) {}
@Post()
async createExpense(@Body() data: ExpenseDto, @Res() response: any) {
const expense = await this.expenseService.createExpense(data);
console.log(expense);
return response.status(201).json({
message: 'success',
data: expense,
});
}
@Get()
async getExpenses(@Res() response: any) {
const expenses = await this.expenseService.getExpenses();
return response.status(200).json({
message: 'success',
data: expenses,
});
}
}
Если вы получаете ошибку зависимости, перейдите к файлу app.module.ts
и удалите ExpenseController
из массива контроллеров.
Вы можете протестировать конечную точку на Postman.
Как реализовать фильтрацию и сортировку
Теперь, когда вы успешно настроили конечные точки расходов, пришло время реализовать функции фильтрации и сортировки в API.
Фильтрация
Фильтрация по сути осуществляется так же, как в Node.js.
Внутри каталога src
создайте новую папку с именем Utils, а внутри этой папки создайте новый файл с именем apiFeatures.ts
.
Внутри этого файла определите класс APIFeatures
. Этот класс будет содержать методы, в которых будут реализованы функции API.
export class APIFeatures {
mongooseQuery: any;
queryString: any;
constructor(mongooseQuery: any, queryString: any) {
this.mongooseQuery = mongooseQuery;
this.queryString = queryString;
}
filter() {
// 1) Filtering
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach((fields) => {
delete queryObj[fields];
});
// console.log(queryObj);
//2) Advanced filtering
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, (match) => `$${match}`);
//console.log(JSON.parse(queryStr));
this.mongooseQuery = this.mongooseQuery.find(JSON.parse(queryStr));
return this;
}
}
В методе фильтра вы создали твердую копию req.query
. Это принято как аргумент в форме запроса.
Перед фильтрацией вы хотите исключить определенные специальные поля, такие как, page
, sort
, limits
и fields
. Эти поля удаляются из жесткой копии объекта, который хранится в переменной queryOBJ
. Вы также хотите использовать операторы MongoDB, такие как GT
или GTE
.
Например, на Postman это то, как вы вводите запрос: ?amount[gt]=100
В MongoDB это будет выглядеть следующим образом: { amount: { $gt: 100 } }
. В методе фильтра мы добавили знака $
к операторам, используя регулярное выражение.
После этого объект проанализируется с использованием метода json.parse ()
, а затем передается в функцию монгуозного запроса. Убедитесь, что вы возвращаете весь класс, набрав это.
Сортировка
Как видите, реализовать фитеринг в Nest.js довольно просто, как и в Node.js.
Теперь пришло время реализовать сортировку. В классе Apifeatures определите другую функцию, называемую сортировкой.
sorting() {
if (this.queryString.sort) {
const sortBy = this.queryString.sort.split(',').join(' ');
// console.log(sortBy);
this.mongooseQuery = this.mongooseQuery.sort(sortBy);
} else {
this.mongooseQuery = this.mongooseQuery.sort('-created');
}
return this;
}
Приведенный выше метод проверяет, существует ли свойство сортировки в объекте запроса. Если это так, вы разделяете строку на ,
если имеется несколько запросов на сортировку. Затем вы присоединяетесь к нему с помощью ''
.
Сделав это, вы связываете его с запросом Mongoose с помощью метода сортировки, который существует во всех документах. Блок else
сортирует документ по дате его создания, если пользователь не указывает какой-либо запрос на сортировку.
Как реализовать ограничение и нумерацию страниц
Поздравляю! Вы реализовали фильтрацию и сортировку. Теперь пришло время реализовать ограничение и нумерацию страниц.
Ограничение
Чтобы ограничить поля, вы можете вызвать метод select()
в запросе Mongoose.
Определите еще один метод в классе:
limit() {
if (this.queryString.fields) {
const fields = this.queryString.fields.split(',').join(' ');
this.mongooseQuery = this.mongooseQuery.select(fields);
} else {
this.mongooseQuery = this.mongooseQuery.select('-__v');
}
return this;
}
И вот.
Пагинация
Чтобы реализовать нумерацию страниц, вам нужно получить page
и limit
из объекта запроса. Затем вы хотите пропустить определенное количество документов, чтобы перейти на нужную страницу. В запросе Mongoose есть метод skip()
и метод limit()
.
pagination() {
// get the page and convert it to a number. If no page set default to 1
const page = this.queryString.page * 1 || 1;
// get limit and if no limit, set limit to 100
const limit = this.queryString.limit * 1 || 100;
// calculate skip value
const skip = (page - 1) * limit;
// chain it to the mongoose query.
this.mongooseQuery = this.mongooseQuery.skip(skip).limit(limit);
// return the object
return this;
}
Сделав это, вы хотите вызвать класс APIfeatures
в классе ExpensesService
в файле Costs.service.ts
.
Замените функцию getExpenses
этим кодом:
async getExpenses(query?: any) {
const features = new APIFeatures(this.expenseModel.find(), query)
.filter()
.sort()
.limit()
.pagination();
//Execute the query
const expenses = await features.mongooseQuery;
return expenses;
}
В функции можно объединить все эти методы в класс APIFeatures
, поскольку каждый метод возвращает объект.
В файле expenses.controller.ts
функция getExpenses
должна выглядеть следующим образом:
@Get()
async getExpenses(@Res() response: any, @Req() request: any) {
const expenses = await this.expenseService.getExpenses(request.query);
return response.status(200).json({
message: 'success',
data: expenses,
});
}
Теперь запустите приложение с помощью npm start:dev
и протестируйте функции API в Postman.
Заключение
Из этого руководства вы узнали, как реализовать сортировку, фильтрацию, ограничение и нумерацию страниц в ваших приложениях Nest.js.
Обладая этими знаниями, вы сможете реализовать эти функции в своих личных проектах, созданных с использованием Nest.js.