← Назад к вопросам

Как реализуется сортировка в 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.