Creating Basic Crud Routes using NestJS and TypeORM

NestJS, TypeORM and Mysql — full example development Crud using @Nestjsx/typeorm-crud

ost software systems have areas with complicated business logic, usually, the core of the system is where this code lives. On the other hand, some features that simply need to store/retrieve data and do not require complex solutions at all. More than often, the core of the system is augmented with simple CRUD (create, read, update, and delete) operations. And, as some CRUD actions do not require custom logic, they can, and in my opinion, should be autogenerated. Besides the obvious benefit of saving on development time, other benefits that should not be overlooked. To name a few:

  • Fewer bugs
  • Consistency out of the box (naming, features, etc.)
  • Requires minimum testing
  • Usually, you get docs for free
  • Now that we have addressed when and why to use autogenerated CURD - - - APIs, let’s explore how can we do it with the nestjs.
  • We are going to build a simple RESTful API for product management.

The following examples do not require any previous knowledge of nestjs

what we need is Nestjs basic setup and TypeORM Integration with Nestjs, we can follow basic steps to create nestjs app

Make sure you have the NestJS CLI installed:

npm i -g @nestjs/cli

Or, if you don’t like to add dependencies to your global node_modules you can always use npx. Anyway, once you have it installed you have to decide on the project name. I will call mine nestjs-crud.

nest new nestjs-crud

Once the project is created simply navigate to the project folder (in this case it will be cd nestjs-crud) and type:

npm run start

By default, the nestjs projects start on the port 3000. If open to the mentioned port in your browser you should see the following: Running nestjs Now that we have the basic project up and running we can move the next section. Install nestjs/crud dependencies As CRUD utilities are not part of the core nestjs framework, we need to install the following dependencies.

npm i @nestjsx/crud @nestjsx/crud-typeorm @nestjs/typeorm typeorm class-transformer class-validator
  • Now we will use TypeORM as ORM for managing Data in application
  • Add Mysql as database in configuration

With basic nestjs setup we have controller, service and module created

import { Module } from '@nestjs/common';
import { AppController } from './app/controllers/app.controller';
import { EntityModule } from './app/module/entity.module';
import { ConfigModule } from './config/config.module';
@Module({
  imports: [
    ConfigModule,
  ],
  controllers: [AppController]
})
export class AppModule {
}

similarly we have some default controller and service, when we run npm run start we can application running on port 3000

Lets create config service to initlize Databse configuration

import { DynamicModule, Module, NotImplementedException } from '@nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { ConfigDBData } from '../config/config.interface';
import { ConfigModule } from '../config/config.module';
import { ConfigService } from '../config/config.service';
import { CommonConfigError } from './common.error';
import { DbConfig } from './db.interface';

@Module({})

export class DatabaseModule {

  public static getConnectionOptions(config: ConfigService, dbconfig: DbConfig): TypeOrmModuleOptions {
    const dbdata = config.get().db;
    console.log(config);
    let connectionOptions: TypeOrmModuleOptions;

    if (!dbdata) {
      throw new CommonConfigError('Database config is missing');
    }
    switch (dbdata.type) {
        case  'mysql':
          connectionOptions = this.getConnectionOptionsMysql(dbdata);
          break;
        case  'sqlite':
          connectionOptions = this.getConnectionOptionsSqlite(dbdata);
          break;
          default:
            throw new NotImplementedException(`Database type '${dbdata.type}' not supported`);
      }
    return {
        ...connectionOptions,
        entities: dbconfig.entities,
        // synchronize: true,
        logging: true,
      };
  }
  private static getConnectionOptionsSqlite(dbdata: any): TypeOrmModuleOptions {
    throw new NotImplementedException(`Database type '${dbdata.type}' not supported`);
  }
  private static getConnectionOptionsMysql(dbdata: ConfigDBData): TypeOrmModuleOptions {
    return {
      type: 'mysql',
      host: dbdata.host,
      port: dbdata.port,
      username: dbdata.user,
      password: dbdata.pass,
      database: dbdata.name,
      charset: dbdata.charset,
      extra: {
        collate: dbdata.collate,
        dialect: dbdata.dialect,
      },
    };
  }
  public static forRoot(dbconfig: DbConfig): DynamicModule {
    return {
      module: DatabaseModule,
      imports: [
        TypeOrmModule.forRootAsync({
          imports: [ConfigModule],
          useFactory: (configService: ConfigService) => DatabaseModule.getConnectionOptions(configService, dbconfig),
          inject: [ConfigService],
        }),
      ],
      controllers: [],
      providers: [],
      exports: [],
    };
  }
}

Now as we Datbase connection we can create Crud controller and Crud service with crud Entity as it will create REST APIs for that Entity

Contact Entity

import {Column, CreateDateColumn, Entity, PrimaryColumn, PrimaryGeneratedColumn, UpdateDateColumn} from 'typeorm';
import {Contact} from '../interface/contact';

@Entity()
export default class ContactEntity implements Contact {

    @PrimaryGeneratedColumn()
    public id: number;

    @PrimaryColumn()
    @Column({
        nullable: false,
        length: 500,
        unique: true,
    })
    public name: string;

    @PrimaryColumn()
    @Column({
        unique: true,
        nullable: false,
    })
    public email: string;

    @Column({
        unique: true,
        nullable: false,
    })
    public phone: string;

    @CreateDateColumn()
    public createdAt;

    @UpdateDateColumn()
    public updatedAt;
}

Controller

import {Controller} from '@nestjs/common';
import { CrudController } from '@nestjsx/crud';
import { Crud } from '@nestjsx/crud';
import ContactEntity from '../module/entity/contact.entity';
import CrudsService from '../module/services/crud.service';

@Crud({
  model: {
    type: ContactEntity,
  },
})
@Controller('contacts')
export class CrudAppController implements CrudController <ContactEntity> {
    constructor(public readonly service: CrudsService) {}
}

Services Crud using nestjsx/typeorm-crud

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { Repository } from 'typeorm';
import ContactEntity from '../entity/contact.entity';

@Injectable()
export  default class CrudsService extends TypeOrmCrudService<ContactEntity> {

  constructor(
    @InjectRepository(ContactEntity) usersRepository: Repository<ContactEntity>,
  ) {
    super(usersRepository);
  }
}

Finally Our Module

import { Module, Type } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as fs from 'fs';
import * as path from 'path';
import { DatabaseModule } from '../../database/database.module';
import ContactEntity from './entity/contact.entity';
import ContactsService from './services/crud.service';
import ItemsService from './services/item.servie';

@Module({
  imports: [
  DatabaseModule.forRoot([ContactEntity]),
  TypeOrmModule.forFeature([ContactEntity]),
  ],
  providers: [ContactsService],
  exports : [ContactsService],
  controllers: [],
})
export class EntityModule {}

We can also get API spec ready for APIs

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('Products')
    .setDescription('Simple CRUD for managing products')
    .setVersion('1.0')
    .addTag('products')
    .build();

  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('docs', app, document);


  await app.listen(3002);
}
bootstrap();

Conclusion

In conclusion, the nestjs framework and especially the @nestjs/crud give us a really rich set of functionality out of the box. Regardless if you are familiar or not with the nestjs framework, it is surprisingly quick to setup and get your first CRUD service.