DEV Community

Tony
Tony

Posted on • Updated on

Simple example custom repository with NestJS 7.x/8.x and TypeORM 😻

In this post I will give you a simple example of a custom repository in NestJS😻 with TypeORM.
For those unfamiliar with or unfamiliar with NestJS, it's a Node.js TypeScript framework that helps you build efficient and scalable enterprise-grade Node.js applications.

So let's get started by creating the NestJS app
Open Terminal and install CLI for NestJS, if you already have it installed, skip this step.

$ npm i -g @nestjs/cli
Enter fullscreen mode Exit fullscreen mode

Then create a NestJS project

$ nest new app
$ cd app
// start the application
$ npm run start: dev
Enter fullscreen mode Exit fullscreen mode

Open the browser on localhost:3000 to verify that hello world is displayed.

then we create a docker-compose.yml file to create the service
MySQL

version: "3"

services:
   mysql:
     image: mysql:5
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: root
       MYSQL_DATABASE: nest
     ports:
       - "3306:3306"
Enter fullscreen mode Exit fullscreen mode

for those who do not know what docker is I leave the link here for more information Docker.

As I mentioned in the previous post, there are many different ways to integrate Nest with databases and they all depend on personal preferences or the needs of the project.

Install TypeORM and MySQL dependencies

$ npm install --save @nestjs/typeorm typeorm@0.2 mysql
Enter fullscreen mode Exit fullscreen mode

Set TypeOrmModule in AppModule

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module ({
   imports: [
     TypeOrmModule.forRoot(),
   ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

create an ormconfig.json file in the project root directory.

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "root",
  "database": "nest",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true,
  "autoLoadEntities": true
}
Enter fullscreen mode Exit fullscreen mode

If you have trouble setting up TypeOrmModule here, make sure Docker is running with docker compose up.

Also make sure the database name inside your TypeOrmModule.forRoot matches the one you have in your docker-compose file.

Well now let's create our entity we call it products.entity.ts:

import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm';

@Entity()
export class Products {
     @PrimaryGeneratedColumn()
     id: number;

     @Column()
     name: string;

     @Column()
     description: string;

     @Column()
     price: string;
}
Enter fullscreen mode Exit fullscreen mode

Create Data Transfer Objects (Dto) class to create the product:

import { MaxLength, IsNotEmpty, IsString } from 'class-validator';

export class CreateProductDto {
    @IsString()
    @IsNotEmpty()
    @MaxLength(20)
    name: string;

    @IsString()
    @IsNotEmpty()
    @MaxLength(100)
    description: string;

    @IsString()
    @IsNotEmpty()
    price: string;
}
Enter fullscreen mode Exit fullscreen mode

Remember to install this package before creating the dto class for the upgrade.

$ npm i @nestjs/mapped-types
Enter fullscreen mode Exit fullscreen mode

Well, now to update the customer data we extend the CreateProductDto class:

import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';

export class UpdateProductDto extends PartialType(CreateProductDto) {}
Enter fullscreen mode Exit fullscreen mode

we call validation pipe in the main.ts file as follows:

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );
  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

Well, now we're going to create a simple service, controller, and product module

$ nest g module products
$ nest g service products
$ nest g controller products
Enter fullscreen mode Exit fullscreen mode

You should now have a customer folder with ProductsModule, ProductsService, and ProductsController inside.

Now let's create a products.repository.ts file to create a custom repository that extends the TypeORM base repository, like this:

import { Repository, EntityRepository } from 'typeorm';
import { Products } from './products.entity';

@EntityRepository(Products)
export class ProductsRepository extends Repository<Products> {}

Enter fullscreen mode Exit fullscreen mode

Our ProductsModule file should look like this:

import { Module } from '@nestjs/common';
import { ProductsController } from './products.controller';
import { ProductsService } from './products.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsRepository } from './products.repository';

@Module({
  imports: [TypeOrmModule.forFeature([ProductsRepository])],
  controllers: [ProductsController],
  providers: [ProductsService],
})

export class ProductsModule {}

Enter fullscreen mode Exit fullscreen mode

ProductsService:

import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Products } from './entities/products.entity';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { ProductsRepository } from './repositories/products.repository';

@Injectable()
export class ProductsService {
    constructor(
        @InjectRepository(ProductsRepository)
        private productsRepository: ProductsRepository,
    ) {}

    public async findAll(): Promise<Products[]> {
        return await this.productsRepository.findAll();
    }

    public async findOne(productId: number): Promise<Products> {
        const product = await this.productsRepository.findById(productId);
        if (!product) {
            throw new NotFoundException(`Product #${productId} not found`);
        }
        return product;
    }

    public async create(
        createProductDto: CreateProductDto,
    ): Promise<Products> {
        try {
          return await this.productsRepository.createProduct(createProductDto);
        } catch (err) {
          throw new HttpException(err, HttpStatus.BAD_REQUEST);
        }
    }

    public async update(
        productId: number,
        updateProductDto: UpdateProductDto,
    ): Promise<Products> {
        const product = await this.productsRepository.findOne(productId);
        if (!product) {
            throw new NotFoundException(`Product #${productId} not found`);
        }
        return this.productsRepository.editProduct(productId, updateProductDto);
    }

    public async remove(productId: number): Promise<void> {
        await this.productsRepository.delete(productId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let's edit our custom repository like this:

import { Repository, EntityRepository } from 'typeorm';
import { Products } from '../entities/products.entity';
import { CreateProductDto } from '../dto/create-product.dto';
import { UpdateProductDto } from '../dto/update-product.dto';
import { Injectable } from '@nestjs/common';

@EntityRepository(Products)
export class ProductsRepository extends Repository<Products> {

    public async findAll(): Promise<Products[]> {
        return await this.find({});
    } 

    public async findById(productId: number): Promise<Products> {
        return await this.findOne(productId);
    }

    public async createProduct(
        createProductDto: CreateProductDto,
    ): Promise<Products> {
        const { name, description, price } = createProductDto;
        const product = new Products();
        product.name = name;
        product.description = description;
        product.price = price;

        await this.save(product);
        return product;
    }

    public async editProduct(
        productId: number,
        updateProductDto: UpdateProductDto,
    ): Promise<Products> {
        const { name, description, price } = updateProductDto;
        const product = await this.findOne(productId);
        product.name = name;
        product.description = description;
        product.price = price;
        await this.save(product);

        return product;
    }

    public async destroy(productId: number): Promise<void> {
        const product = await this.findOne(productId);
        await this.remove(product);
    } 
}


Enter fullscreen mode Exit fullscreen mode

ProductsController:

import {
    Controller,
    Post,
    Body,
    Get,
    Patch,
    Param,
    Delete,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { Products } from './entities/products.entity';

@Controller('/api/products')
export class ProductsController {
    constructor(private productsService: ProductsService) { }

    @Get()
    public async findAll(): Promise<Products[]> {
        return await this.productsService.findAll();
    }

    @Get('/:productId')
    public async findOne(@Param('productId') productId: number): Promise<Products> {
        return await this.productsService.findOne(productId);
    }

    @Post()
    public async create(
        @Body() createProductsDto: CreateProductDto,
    ): Promise<Products> {
        return await this.productsService.create(createProductsDto);
    }

    @Patch('/:productId')
    public async update(
        @Body() updateProductDto: UpdateProductDto,
        @Param('productId') productId: number,
    ): Promise<Products> {
        const product = await this.productsService.update(
            productId,
            updateProductDto,
        );
        return product;
    }

    @Delete('/:productId')
    public async delete(@Param('productId') productId: number): Promise<void> {
        const product = await this.findOne(productId);
        if (!product) {
            throw new NotFoundException(`Product #${product} not found`);
        }

        return await this.productsService.remove(productId);
    }
}

Enter fullscreen mode Exit fullscreen mode

Getting with Curl Products:

    $ curl -H 'content-type: application/json' -v -X GET http://127.0.0.1:3000/api/products  
    $ curl -H 'content-type: application/json' -v -X GET http://127.0.0.1:3000/api/products/:id 
    $ curl -H 'content-type: application/json' -v -X POST -d '{"name": "Product #1","description": "Lorem ipsum", "price": "19.99"}' http://127.0.0.1:3000/api/products 
    $ curl -H 'content-type: application/json' -v -X PUT -d '{"name": "Product #1","description": "Lorem ipsum", "price": "19.99"}' http://127.0.0.1:3000/api/products/:id 
    $ curl -H 'content-type: application/json' -v -X DELETE http://127.0.0.1:3000/api/products/:id 
Enter fullscreen mode Exit fullscreen mode

I hope it will be useful in building your applications with NestJS 😻

That's it πŸ˜€
For anything write me in the comments πŸ˜‰

Discussion (9)

Collapse
robertsci profile image
robertsci • Edited on

In case someone wanted to have the folder structure. For me it worked like this.

src
|---products
|----------|----- dto
|----------|-----|---- create-product-dto.ts
|----------|-----|---- update-product-dto.ts

|----------|-----entities
|----------|-----|---products.entity.ts
|----------|-----repositories
|----------|-----|----products.repository.ts
|----------|-----products.controller.ts

|----------|-----products.module.ts
|----------|-----products.service.ts

Here you can check how to also add swagger after you make sure your project starts
docs.nestjs.com/openapi/introduction

Thank you to the author !

Collapse
micalevisk profile image
Micael Levi L. C.

is there any reason to use @Injectable in custom repositories like you did in ProductsRepository? Will this work without adding it to the providers array?

Collapse
micalevisk profile image
Micael Levi L. C. • Edited on

nvm. I just tried this and confirm that we can't inject things on custom repository because they aren't controlled by Nest container.

More on this here: stackoverflow.com/a/65227341/5290447

Collapse
tony133 profile image
Tony Author • Edited on

my writing error, thanks for the correct report, it works even without the @injectable decorator, I corrected the post, in fact at the beginning of the post I had written it correctly.

Thread Thread
fabianaasara profile image
Fabiana Asara

Yes, it does work without @Injectable it's happened to me when I did a tech test and it was pointed out to me at the interview. But I still don't know why, it shouldn't work?

Thread Thread
fabianaasara profile image
Fabiana Asara

Btw, thanks for this post!! πŸ”₯

Collapse
reisap profile image
Reisa Prasaptaraya

thanks,,this is help me a lot...

Collapse
mahdipishguy profile image
Mahdi Pishguy

thanks so much, can we use TypeOrm with mongoDb?

Collapse
tony133 profile image
Tony Author • Edited on

yes, you can use typeorm with mongodb here is an example base, without custom repositoy: github.com/nestjs/nest/tree/master...