npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mee4dy/crud-nestjs

v1.0.24

Published

Модуль для NestJS, предоставляющий декларативный и расширяемый CRUD-контроллер с минимальной конфигурацией. Позволяет быстро создавать REST API для любых моделей с поддержкой фильтрации, сортировки, пагинации, скопов и гибкой настройки.

Downloads

148

Readme

@mee4dy/crud-nestjs

Модуль для NestJS, предоставляющий декларативный и расширяемый CRUD-контроллер с минимальной конфигурацией. Позволяет быстро создавать REST API для любых моделей с поддержкой фильтрации, сортировки, пагинации, скопов и гибкой настройки.

Преимущества

  • Декларативный подход через декоратор @Crud
  • Не требует создания сервисов и наследования
  • Гибкая настройка разрешённых параметров (allowedParams)
  • Поддержка scopes (динамические параметры по каждому эндпоинту)
  • Единый формат ответа и ошибок
  • Лёгкая интеграция с клиентом (@mee4dy/crud-client)
  • Расширяемость и чистый код

Установка

npm install @mee4dy/crud-nestjs

Быстрый старт

  1. Импортируйте пакет и используйте декоратор @Crud в контроллере:
import { Controller } from '@nestjs/common';
import { Crud } from '@mee4dy/crud-nestjs';
import { Post } from './posts.model';

@Crud({
  repository: () => Posts,
})
@Controller('posts')
export class PostsController {}
  1. Подключите модель к SequelizeModule в модуле:
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { PostsController } from './posts.controller';
import { Posts } from './posts.model';

@Module({
  imports: [SequelizeModule.forFeature([Posts])],
  controllers: [PostsController],
})
export class PostsModule {}

Структура опций для декоратора @Crud

type CrudDecoratorConfig = {
  /** Функция, возвращающая модель Sequelize для работы с БД */
  repository: () => any;

  /** Имя поля первичного ключа (по умолчанию 'id') */
  pk?: string;

  /** Включение/отключение CRUD-методов */
  endpoints?: {
    items?: boolean; // GET /items - получение списка
    item?: boolean; // GET /items/:pk - получение одного элемента
    create?: boolean; // POST /items - создание
    update?: boolean; // PUT /items/:pk - обновление
    delete?: boolean; // DELETE /items/:pk - удаление
  };

  /** Настройка разрешённых параметров из query string */
  query?: {
    allowedParams?: {
      filters?: string[] | boolean; // Разрешённые поля для фильтрации
      orders?: string[] | boolean; // Разрешённые поля для сортировки
      groups?: string[] | boolean; // Разрешённые поля для группировки
      limit?: boolean; // Разрешить пагинацию (limit)
      offset?: boolean; // Разрешить пагинацию (offset)
      fields?: string[]; // Разрешённые поля для выборки
      fieldsExclude?: string[]; // Поля для исключения из выборки
    };
  };

  /** Параметры по умолчанию для всех запросов */
  params?: {
    default?: Partial<CrudParams>;
  };

  /** Динамические параметры для каждого эндпоинта */
  scopes?: {
    global?: (req: any) => Partial<{ params: Partial<CrudParams> }>; // Применяется ко всем эндпоинтам
    items?: (req: any) => Partial<{ params: Partial<CrudParams> }>; // Только для получения списка
    item?: (req: any) => Partial<{ params: Partial<CrudParams> }>; // Только для получения одного элемента
    create?: (req: any) => Partial<{ params: Partial<CrudParams> }>; // Только для создания
    update?: (req: any) => Partial<{ params: Partial<CrudParams> }>; // Только для обновления
    delete?: (req: any) => Partial<{ params: Partial<CrudParams> }>; // Только для удаления
  };
};

Тип CrudParams

CrudParams — это основной тип для описания параметров запроса к базе данных.

interface CrudParams {
  filters?: CrudParamsFilter; // Фильтры для WHERE условий
  join?: CrudJoin[]; // Join связи
  fields?: CrudFields; // Настройка выборки полей
  orders?: CrudOrder[]; // Сортировка (ORDER BY)
  groups?: string[]; // Группировка (GROUP BY)
  limit?: number; // Лимит записей
  offset?: number; // Смещение для пагинации
  repositoryParams?: Partial<FindOptions>; // Прямые параметры Sequelize
  repository?: () => any; // Динамическая смена репозитория со scopes
}

Фильтры (filters)

type CrudParamsFilter = {
  [field: string]: CrudFieldFilter;
};

type CrudFieldFilter =
  | string // Простое равенство: { user_id: "123" }
  | number // Простое равенство: { status: 1 }
  | boolean // Простое равенство: { active: true }
  | { op: 'eq'; value: string | number | boolean } // Явное равенство
  | { op: 'like'; value: string } // LIKE поиск
  | { op: 'range' | 'period'; from: number | string; to: number | string }; // Диапазон значений

Примеры фильтров:

// В коде:
const filters = {
  user_id: 1,                                    // Простое равенство
  title: { op: 'like', value: '%test%' },       // LIKE поиск
  created_at: { op: 'range', from: '2024-01-01', to: '2024-12-31' }, // Диапазон дат
  status: { op: 'eq', value: 'active' }         // Явное равенство
};

// HTTP запрос:
GET /posts?filters[user_id]=1&filters[title][op]=like&filters[title][value]=test&filters[created_at][op]=range&filters[created_at][from]=2024-01-01&filters[created_at][to]=2024-12-31

Пример объединения условий:

// CRUD фильтры + repositoryParams
const params: CrudParams = {
  filters: { user_id: 1, status: 'active' },
  repositoryParams: {
    where: {
      created_at: {
        [Op.lte]: '2025-07-28 12:00:00',
      },
    },
  },
};

// Результат объединения:
// WHERE user_id = 1 AND status = 'active' AND created_at <= '2025-07-28 12:00:00'

Сортировка (orders)

type CrudOrder = [string, 'asc' | 'desc'];  // [поле, направление]

// В коде:
const orders = [
  ['created_at', 'desc'],  // Сначала новые
  ['title', 'asc'],        // По алфавиту
  ['id', 'desc']           // По ID убывание
];

// HTTP запрос:
GET /posts?orders[0][0]=created_at&orders[0][1]=desc&orders[1][0]=title&orders[1][1]=asc

Группировка (groups)

// В коде:
const groups = ['user_id', 'status'];

// HTTP запрос:
GET /posts?groups[0]=user_id&groups[1]=status

Пагинация (limit, offset)

// В коде:
const limit = 20;   // Количество записей на странице
const offset = 40;  // Пропустить первые 40 записей (для 3-й страницы)

// HTTP запрос:
GET /posts?limit=20&offset=40

Выборка полей (fields)

type CrudFields = {
  include?: [Literal, CrudField][]; // Дополнительные SQL выражения
  exclude?: CrudField[]; // Исключаемые поля
  aggregate?: [Literal, CrudField][]; // Агрегационные поля (для группировки)
};

Примеры использования:

// Обычная выборка полей
const fields = {
  include: [
    [Sequelize.literal('CONCAT(name, " (", email, ")")'), 'full_info'],
    [Sequelize.literal('COUNT(*)'), 'total_count'],
  ],
  exclude: ['password', 'deleted_at'],
};

// Агрегационные поля для группировки
const fields = {
  aggregate: [
    [Sequelize.col('name'), 'name'],
    [Sequelize.col('type'), 'type'],
  ],
  include: [
    [Sequelize.literal('COUNT(*)'), 'total_count'],
    [Sequelize.literal('MAX(created_at)'), 'last_created'],
  ],
};

// Автоматическое исключение при группировке
const params: CrudParams = {
  groups: ['name', 'type'],
  // Автоматически исключатся все поля кроме name и type
};

Особенности работы с группировкой:

  1. Автоматическое исключение - при наличии groups автоматически исключаются все поля модели, которые не участвуют в группировке
  2. Агрегационные поля - поле aggregate позволяет явно указать поля для группировки, игнорируя автоматическое исключение
  3. Приоритет aggregate - если указано aggregate, автоматическое исключение не применяется

Примеры SQL запросов:

// С группировкой без override
const params: CrudParams = {
  groups: ['name', 'type'],
  fields: {
    include: [[Sequelize.literal('COUNT(*)'), 'total']],
  },
};
// SQL: SELECT name, type, COUNT(*) as total FROM table GROUP BY name, type

// С группировкой и aggregate
const params: CrudParams = {
  groups: ['name', 'type'],
  fields: {
    aggregate: [
      [Sequelize.col('name'), 'name'],
      [Sequelize.col('type'), 'type'],
    ],
    include: [
      [Sequelize.literal('COUNT(*)'), 'total'],
      [Sequelize.literal('MAX(date)'), 'last_date'],
    ],
  },
};
// SQL: SELECT name, type, COUNT(*) as total, MAX(date) as last_date FROM table GROUP BY name, type

Связи (join)

type CrudJoin = {
  repository: () => any; // Функция, возвращающая модель Sequelize
  fields?: CrudFields; // Настройка полей для связанной модели
  join?: CrudJoin[]; // Вложенные join для связанной модели
};

Примеры join связей:

Особенности настройки полей в join:

  1. fields: CrudFields - настройка полей для связанной модели
  2. fields: false - отключить все поля из join (только для фильтрации)
  3. fields: undefined - использовать все поля по умолчанию
// Простой join с пользователем
const join = [
  {
    repository: () => User,
    fields: {
      include: [[Sequelize.literal('CONCAT(name, " (", email, ")")'), 'full_info']],
      exclude: ['password', 'deleted_at'],
    },
  },
];

// Join без полей (только для фильтрации)
const join = [
  {
    repository: () => User,
    fields: false, // Отключить все поля из join
  },
];

// Вложенный join: посты с комментариями и пользователями комментариев
const join = [
  {
    repository: () => Comment,
    fields: {
      include: [[Sequelize.literal('CONCAT("[", id, "] ", text)'), 'formatted_text']],
    },
    join: [
      {
        repository: () => User,
        fields: {
          exclude: ['password', 'deleted_at'],
        },
      },
    ],
  },
];

// Множественные join
const join = [
  {
    repository: () => User,
    fields: {
      exclude: ['password'],
    },
  },
  {
    repository: () => Category,
    fields: {
      include: [[Sequelize.literal('UPPER(name)'), 'upper_name']],
    },
  },
];

// Join только для фильтрации (без полей в результате)
const join = [
  {
    repository: () => User,
    fields: false, // Не включать поля пользователя в результат
  },
  {
    repository: () => Category,
    fields: {
      include: [[Sequelize.literal('UPPER(name)'), 'category_name']],
    },
  },
];
// Результат: только поля поста + category_name, без полей пользователя

Прямые параметры Sequelize (repositoryParams)

Поле repositoryParams позволяет передавать любые параметры напрямую в Sequelize для формирования сложных кастомных запросов.

// В коде:
const repositoryParams = {
  subQuery: false,
  distinct: true,
  lock: true,
  paranoid: false,
  // любые другие параметры Sequelize FindOptions
};

Примеры использования:

// Отключение soft delete для конкретного запроса
const params: CrudParams = {
  filters: { user_id: 1 },
  repositoryParams: {
    paranoid: false,
  },
};

// Использование блокировки для критических операций
const params: CrudParams = {
  filters: { id: 123 },
  repositoryParams: {
    lock: true,
  },
};

// Сложные запросы с подзапросами
const params: CrudParams = {
  repositoryParams: {
    subQuery: false,
    distinct: true,
    attributes: {
      include: [
        [Sequelize.literal('(SELECT COUNT(*) FROM comments WHERE comments.post_id = Posts.id)'), 'comments_count'],
      ],
    },
  },
};
// Результат: Объединяется с любыми существующими фильтрами и атрибутами

Важно: Параметры из repositoryParams объединяются с параметрами, сгенерированными из других полей CrudParams, используя глубокое слияние объектов. Это позволяет комбинировать CRUD фильтры с прямыми Sequelize условиями.

Примеры контроллеров

Минимальный контроллер

@Crud({ repository: () => Users })
@Controller('users')
export class UsersController {}

Контроллер с ограничением параметров

@Crud({
  repository: () => Posts,
  query: {
    allowedParams: {
      filters: ['id', 'user_id'],
      orders: ['id', 'created_at'],
      limit: true,
      offset: true,
    },
  },
})
@Controller('posts')
export class PostsController {}

Контроллер с default params и scopes по эндпоинтам

@Crud({
  repository: () => Comments,
  params: {
    default: {
      orders: [['id', 'desc']],
      limit: 10,
    },
  },
  scopes: {
    global: (req) => ({
      params: {
        filters: {
          deleted_at: null,
        },
      },
    }),
    items: (req) => ({
      params: {
        filters: {
          user_id: req.user.id,
        },
      },
    }),
    create: (req) => ({
      params: {
        filters: {
          user_id: req.user.id,
        },
      },
    }),
  },
})
@Controller('comments')
export class CommentsController {}

Контроллер с join связями

@Crud({
  repository: () => Posts,
  params: {
    default: {
      join: [
        {
          repository: () => Users,
          fields: {
            exclude: ['password', 'deleted_at'],
          },
        },
      ],
    },
  },
  scopes: {
    item: (req) => ({
      params: {
        join: [
          {
            repository: () => Comments,
            fields: {
              include: [[Sequelize.literal('CONCAT("[", id, "] ", text)'), 'formatted_text']],
            },
            join: [
              {
                repository: () => Users,
                fields: {
                  exclude: ['password'],
                },
              },
            ],
          },
        ],
      },
    }),
  },
})
@Controller('posts')
export class PostsController {}

Контроллер с Sequelize scopes

@Crud({
  repository: () => Posts.scope('active'),
})
@Controller('posts')
export class PostsController {}

// Несколько scopes
@Crud({
  repository: () => Posts.scope(['active', 'withUser', 'withComments']),
})
@Controller('posts')
export class PostsController {}

// Динамические scopes через params
@Crud({
  repository: () => Posts.scope('active'),
  scopes: {
    items: (req) => ({
      params: {
        repository: () => Posts.scope(['published', 'withComments']),
      },
    }),
    item: (req) => ({
      params: {
        repository: () => Posts.scope(['withUser', 'withComments', 'withLikes']),
      },
    }),
  },
})
@Controller('posts')
export class PostsController {}

Формат ответа

Все ответы возвращаются в едином формате:

{
  "status": true,
  "data": {
    "items": [ ... ],
    "item": { ... }
  }
}

В случае ошибки:

{
  "status": false,
  "error": {
    "message": "...",
    "statusCode": 400,
    "errorType": "..."
  }
}

Обработка ошибок

  • Все ошибки автоматически форматируются через фильтр CrudExceptionFilter
  • Для отключённых эндпоинтов выбрасывается EndpointDisabledException

Интеграция с клиентом

Пакет полностью совместим с @mee4dy/crud-client для фронтенда.

Совместимость

  • NestJS >= 9
  • Sequelize >= 6
  • Node.js >= 16

Лицензия

MIT