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
Then create a NestJS project
$ nest new app
$ cd app
// start the application
$ npm run start: dev
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"
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
Set TypeOrmModule in AppModule
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module ({
imports: [
TypeOrmModule.forRoot(),
],
})
export class AppModule {}
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
}
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;
}
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;
}
Remember to install this package before creating the dto class for the upgrade.
$ npm i @nestjs/mapped-types
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) {}
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();
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
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> {}
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 {}
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);
}
}
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);
}
}
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);
}
}
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
I hope it will be useful in building your applications with NestJS 😻
That's it 😀
For anything write me in the comments 😉
Top comments (9)
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 !
is there any reason to use
@Injectable
in custom repositories like you did inProductsRepository
? Will this work without adding it to theproviders
array?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
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.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?Btw, thanks for this post!! 🔥
thanks,,this is help me a lot...
thanks so much, can we use TypeOrm with mongoDb?
yes, you can use typeorm with mongodb here is an example base, without custom repositoy: github.com/nestjs/nest/tree/master...