CRUD

This chapter applies only to TypeScript

CRUD package (@nestjsx/crud) helps you create CRUD controllers and services with ease and provides a bunch of the features for your RESTful API out of the box:

  • Database agnostic extendable CRUD controller
  • Query string parsing with filtering, pagination, sorting, relations, nested relations, cache, etc.
  • Framework agnostic package with query builder for frontend usage
  • Query, path params and DTO validation
  • Overriding controller methods with ease
  • Tiny but powerful configuration (including global configuration)
  • Additional helper decorators
  • Swagger documentation

warning Notice So far, @nestjsx/crud only supports TypeORM, but other ORMs like Sequelize and Mongoose will be included in the near future. So in this article, you’ll learn how to create CRUD controllers and services using TypeORM. We assume that you have already successfully installed and set up the @nestjs/typeorm package. To learn more, see here.

Getting started

To start creating CRUD functionality we have to install all required dependencies:

  1. npm i --save @nestjsx/crud @nestjsx/crud-typeorm class-transformer class-validator

Assuming that you already have some entities in your project:

  1. @@filename(hero.entity)
  2. import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
  3. @Entity()
  4. export class Hero {
  5. @PrimaryGeneratedColumn()
  6. id: number;
  7. @Column()
  8. name: string;
  9. @Column({ type: 'number' })
  10. power: number;
  11. }

The first step we need to do is to create a service:

  1. @@filename(heroes.service)
  2. import { Injectable } from '@nestjs/common';
  3. import { InjectRepository } from '@nestjs/typeorm';
  4. import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
  5. import { Hero } from './hero.entity';
  6. @Injectable()
  7. export class HeroesService extends TypeOrmCrudService<Hero> {
  8. constructor(@InjectRepository(Hero) repo) {
  9. super(repo);
  10. }
  11. }

We’re done with the service so let’s create a controller:

  1. @@filename(heroes.controller)
  2. import { Controller } from '@nestjs/common';
  3. import { Crud } from '@nestjsx/crud';
  4. import { Hero } from './hero.entity';
  5. import { HeroesService } from './heroes.service';
  6. @Crud({
  7. model: {
  8. type: Hero,
  9. },
  10. })
  11. @Controller('heroes')
  12. export class HeroesController {
  13. constructor(public service: HeroesService) {}
  14. }

And finally, we need to wire up everything in our module:

  1. @@filename(heroes.module)
  2. import { Module } from '@nestjs/common';
  3. import { TypeOrmModule } from '@nestjs/typeorm';
  4. import { Hero } from './hero.entity';
  5. import { HeroesService } from './heroes.service';
  6. import { HeroesController } from './heroes.controller';
  7. @Module({
  8. imports: [TypeOrmModule.forFeature([Hero])],
  9. providers: [HeroesService],
  10. controllers: [HeroesController],
  11. })
  12. export class HeroesModule {}

warning Notice Do not forget to import the HeroesModule into the root ApplicationModule.

Afterwards, your Nest application will have these newly created endpoints:

  • GET /heroes - get many heroes.
  • GET /heroes/:id - get one hero.
  • POST /heroes/bulk - create many heroes.
  • POST /heroes - create one hero.
  • PATCH /heroes/:id - update one hero.
  • PUT /heroes/:id - replace one hero.
  • DELETE /heroes/:id - delete one hero.

Filtering and pagination

CRUD provides rich tools for filtering and pagination. Example request:

info Request GET /heroes?select=name&filter=power||gt||90&sort=name,ASC&page=1&limit=3

In this example, we requested the list of heroes and selected name attribute only, where the power of a hero is greater than 90, and set a result limit to 3 within page 1, and sorted by name in ASC order.

The response object will be similar to this one:

  1. {
  2. "data": [
  3. {
  4. "id": 2,
  5. "name": "Batman"
  6. },
  7. {
  8. "id": 4,
  9. "name": "Flash"
  10. },
  11. {
  12. "id": 3,
  13. "name": "Superman"
  14. }
  15. ],
  16. "count": 3,
  17. "total": 14,
  18. "page": 1,
  19. "pageCount": 5
  20. }

warning Notice Primary columns persist in the resource response object whether they were requested or not. In our case, it’s an id column.

The complete list of query params and filter operators can be found in the project’s Wiki.

Relations

Another feature that is worth mentioning is “relations”. In your CRUD controller, you can specify the list of entity’s relations which are allowed to fetch within your API calls:

  1. @Crud({
  2. model: {
  3. type: Hero,
  4. },
  5. join: {
  6. profile: {
  7. exclude: ['secret'],
  8. },
  9. faction: {
  10. eager: true,
  11. only: ['name'],
  12. },
  13. },
  14. })
  15. @Controller('heroes')
  16. export class HeroesController {
  17. constructor(public service: HeroesService) {}
  18. }

After specifying allowed relations in the @Crud() decorator options, you can make the following request:

info Request GET /heroes/25?join=profile||address,bio

The response will contain a hero object with a joined profile which will have address and bio columns selected.

Also, the response will contain a faction object with the name column selected because it was set to eager: true and thus persists in every response.

You can find more information about relations in the project’s WiKi.

Path params validation

By default, CRUD will create a slug with the name id and will be validating it as a number.

But there is a possibility to change this behavior. Assume, your entity has a primary column _id - a UUID string - and you need to use it as a slug for your endpoints. With these options it’s easy to do:

  1. @Crud({
  2. model: {
  3. type: Hero,
  4. },
  5. params: {
  6. _id: {
  7. field: '_id',
  8. type: 'uuid',
  9. primary: true,
  10. },
  11. },
  12. })
  13. @Controller('heroes')
  14. export class HeroesController {
  15. constructor(public service: HeroesService) {}
  16. }

For more params options please see the project’s Wiki.

Request body validation

Request body validation is performed out of the box by applying Nest ValidationPipe on each POST, PUT, PATCH request. We use model.type from @Crud() decorator options as a DTO that describes validation rules.

In order to do that properly we use validation groups:

  1. @@filename(hero.entity)
  2. import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
  3. import { IsOptional, IsDefined, IsString, IsNumber } from 'class-validator';
  4. import { CrudValidationGroups } from '@nestjsx/crud';
  5. const { CREATE, UPDATE } = CrudValidationGroups;
  6. @Entity()
  7. export class Hero {
  8. @IsOptional({ always: true })
  9. @PrimaryGeneratedColumn()
  10. id: number;
  11. @IsOptional({ groups: [UPDATE] })
  12. @IsDefined({ groups: [CREATE] })
  13. @IsString({ always: true })
  14. @Column()
  15. name: string;
  16. @IsOptional({ groups: [UPDATE] })
  17. @IsDefined({ groups: [CREATE] })
  18. @IsNumber({}, { always: true })
  19. @Column({ type: 'number' })
  20. power: number;
  21. }

warning Notice The full support of separate DTO classes for create and update actions is one of the main priorities for the next CRUD release.

Routes options

You can disable or enable only some particular routes that are being generated by applying @Crud() decorator:

  1. @Crud({
  2. model: {
  3. type: Hero,
  4. },
  5. routes: {
  6. only: ['getManyBase'],
  7. getManyBase: {
  8. decorators: [
  9. UseGuards(HeroAuthGuard)
  10. ]
  11. }
  12. }
  13. })
  14. @Controller('heroes')
  15. export class HeroesController {
  16. constructor(public service: HeroesService) {}
  17. }

Also, you can apply any method decorators by passing them to the specific route decorators array. This is convenient when you want to add some decorators without overriding base methods.

Documentation

The examples in this chapter cover only some of the CRUD features. You can find answers to many more usage questions on the project’s Wiki page.