← Назад к вопросам
Как реализуется сортировка в NestJS?
2.0 Middle🔥 121 комментариев
#Базы данных и SQL#Фреймворки и библиотеки
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализуется сортировка в NestJS?
Сортировка в NestJS реализуется на разных уровнях архитектуры: от параметров запроса до работы с базой данных. Это критический компонент для построения масштабируемых приложений.
1. Парсинг параметров сортировки из запроса
Обычно сортировка передаётся через query-параметры:
@Get()
async findAll(@Query() query: FindAllQuery) {
// query.sort = "name:ASC,createdAt:DESC"
return this.userService.findAll(query);
}
2. DTO для валидации
import { IsEnum, IsOptional } from 'class-validator';
export class FindAllQuery {
@IsOptional()
@IsString()
sort?: string; // "field:direction,field2:direction"
@IsOptional()
@IsNumber()
limit?: number = 10;
@IsOptional()
@IsNumber()
offset?: number = 0;
}
export enum SortDirection {
ASC = 'ASC',
DESC = 'DESC',
}
3. Парсинг и валидация в сервисе
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async findAll(query: FindAllQuery) {
const { sort, limit = 10, offset = 0 } = query;
const orderBy = this.parseSortString(sort);
const [data, total] = await this.usersRepository.findAndCount({
order: orderBy,
take: limit,
skip: offset,
});
return {
data,
total,
limit,
offset,
};
}
private parseSortString(
sortString?: string,
): Record<string, 'ASC' | 'DESC'> {
if (!sortString) return { createdAt: 'DESC' }; // сортировка по умолчанию
const order: Record<string, 'ASC' | 'DESC'> = {};
sortString.split(',').forEach((sort) => {
const [field, direction] = sort.split(':');
if (field && direction) {
order[field] = direction.toUpperCase() as 'ASC' | 'DESC';
}
});
return order;
}
}
4. Использование QueryBuilder для сложных случаев
TypeORM QueryBuilder даёт больше контроля:
async findAll(query: FindAllQuery) {
const { sort, limit = 10, offset = 0 } = query;
const orderBy = this.parseSortString(sort);
let qb = this.usersRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile');
// Применяем сортировку
Object.entries(orderBy).forEach(([field, direction]) => {
qb = qb.addOrderBy(`user.${field}`, direction);
});
const [data, total] = await qb
.take(limit)
.skip(offset)
.getManyAndCount();
return { data, total };
}
5. Безопасность при сортировке
Критически важно валидировать поля для сортировки, чтобы избежать SQL-injection:
private readonly ALLOWED_SORT_FIELDS = ['name', 'email', 'createdAt'];
private parseSortString(
sortString?: string,
): Record<string, 'ASC' | 'DESC'> {
if (!sortString) return { createdAt: 'DESC' };
const order: Record<string, 'ASC' | 'DESC'> = {};
sortString.split(',').forEach((sort) => {
const [field, direction] = sort.split(':');
// Валидация поля
if (!this.ALLOWED_SORT_FIELDS.includes(field)) {
throw new BadRequestException(`Invalid sort field: ${field}`);
}
// Валидация направления
if (!['ASC', 'DESC'].includes(direction?.toUpperCase())) {
throw new BadRequestException(`Invalid sort direction: ${direction}`);
}
order[field] = direction.toUpperCase() as 'ASC' | 'DESC';
});
return order;
}
6. Интеграция с Pagination
Часто сортировку комбинируют с пагинацией:
@Get()
async findAll(
@Query() query: FindAllQuery,
) {
return this.userService.findAll(query);
}
// GET /users?sort=name:ASC,createdAt:DESC&limit=20&offset=0
Главные правила
- Всегда валидируй поля для сортировки
- Используй белый список разрешённых полей
- Сортировка по умолчанию по ID или createdAt
- Комбинируй с пагинацией для больших датасетов
- В продакшене добавляй индексы на часто используемые поля
Такая реализация обеспечивает безопасность, производительность и удобство использования API.