Building a Powerful CRUD API with NestJS and TypeORM: A Comprehensive Guide

Brian
5 min readApr 1, 2023

--

Instead of directly working with SQL databases, I have found that using TypeORM significantly simplifies the process of working with databases and reduces development time. Therefore, I would like to recommend using TypeORM for anyone who wants to work with databases more efficiently.

TypeORM is an Object-Relational Mapping (ORM) library for TypeScript and JavaScript that allows developers to work with relational databases using object-oriented programming techniques. It provides a rich set of features for defining database schema, querying data, and managing database connections. TypeORM supports multiple database systems, including MySQL, PostgreSQL, Oracle, and SQLite.

NestJS provides built-in support for TypeORM, allowing developers to use it to interact with databases in their applications. NestJS uses dependency injection to provide instances of Repository classes to other components in the application. Developers can define a Provider for a Repository class, and then inject it into other components that need to interact with the database. NestJS also provides built-in support for defining controllers, services, and modules that use TypeORM repositories, making it easy to build complex database-driven applications.

Here’s an example of a simple NestJS CRUD application using PostgreSQL as the database and the TypeORM library as the ORM.

First, let’s create a new PostgreSQL database and create a table called todos:

CREATE DATABASE nestjs_postgres_crud;
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
completed BOOLEAN NOT NULL DEFAULT FALSE
);

In PostgreSQL, serial is a pseudo data type used to define an auto-incrementing column. When a column is defined as serial, PostgreSQL automatically creates a sequence object and sets the default value of the column to the next value of the sequence. This means that every time a new row is inserted into the table, the serial column value will be automatically incremented by 1.

Next, let’s create a Todo entity using TypeORM:

// src/todo/todo.entity.ts

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

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

@Column()
title: string;

@Column({ nullable: true })
description: string;

@Column({ default: false })
completed: boolean;
}

TypeORM provides a number of decorators that can be used to define the structure of entities in a database. Here are some of the most commonly used decorators:

  • @Entity(): This decorator is used to define a new entity in TypeORM. It should be applied to the class that represents the entity.
  • @Column(): This decorator is used to define a new column in an entity. It should be applied to the property that represents the column.
  • @PrimaryGeneratedColumn(): This decorator is used to define a new primary key column in an entity. It should be applied to the property that represents the primary key.

Now, let’s create a TodoService that will handle the CRUD operations:

// src/todo/todo.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Todo } from './todo.entity';

@Injectable()
export class TodoService {
constructor(
@InjectRepository(Todo)
private readonly todoRepository: Repository<Todo>,
) {}

async findAll(): Promise<Todo[]> {
return this.todoRepository.find();
}

async findById(id: number): Promise<Todo> {
return this.todoRepository.findOne(id);
}

async create(todo: Todo): Promise<Todo> {
return this.todoRepository.save(todo);
}

async update(id: number, todo: Todo): Promise<Todo> {
await this.todoRepository.update(id, todo);
return this.todoRepository.findOne(id);
}

async delete(id: number): Promise<void> {
await this.todoRepository.delete(id);
}
}

@InjectRepository is a decorator provided by the @nestjs/typeorm package in NestJS that can be used to inject a TypeORM repository instance into a service or controller.

When you add the @InjectRepository decorator to a class property or constructor parameter, NestJS automatically injects an instance of the corresponding repository at runtime. For example, in the TodoService class, the following line:

@InjectRepository(Todo)
private readonly todoRepository: Repository<Todo>,

injects an instance of the Repository<Todo> class, which represents a TypeORM repository for the Todo entity.

The @InjectRepository decorator works by leveraging the dependency injection system provided by NestJS. Under the hood, it creates a new instance of the repository for each request, and ensures that the same instance is used throughout the entire request lifecycle. This helps to avoid issues with stateful repositories and ensures that each request is handled independently.

The Repository class in TypeORM provides several CRUD (Create, Read, Update, Delete) methods for working with entities in a database. Here are some of the most commonly used methods:

  • find(options?: FindManyOptions<Entity>): Promise<Entity[]>: Returns an array of all entities that match the specified FindManyOptions criteria, or an empty array if no entities are found.
  • findOne(id: string | number | Date | ObjectID, options?: FindOneOptions<Entity>): Promise<Entity | undefined>: Returns a single entity that matches the specified ID or criteria, or undefined if no entity is found.
  • save(entity: Entity, options?: SaveOptions): Promise<Entity>: Saves the specified entity to the database and returns the saved entity.
  • update(criteria: string | number | Date | ObjectID | FindConditions<Entity>, partialEntity: QueryDeepPartialEntity<Entity>): Promise<UpdateResult>: Updates one or more entities that match the specified criteria, and returns an object containing information about the update operation.
  • delete(criteria: string | number | Date | ObjectID | FindConditions<Entity>): Promise<DeleteResult>: Deletes one or more entities that match the specified criteria, and returns an object containing information about the delete operation.
  • count(options?: FindManyOptions<Entity>): Promise<number>: Returns the total number of entities that match the specified FindManyOptions criteria.

These methods allow you to perform basic CRUD operations on entities in a database using a concise and expressive API. They also provide powerful options for querying, sorting, and filtering entities, making it easy to write complex database queries without having to write raw SQL.

Finally, let’s create a TodoController that will handle the HTTP requests:

// src/todo/todo.controller.ts

import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { Todo } from './todo.entity';
import { TodoService } from './todo.service';

@Controller('todos')
export class TodoController {
constructor(private readonly todoService: TodoService) {}

@Get()
async findAll(): Promise<Todo[]> {
return this.todoService.findAll();
}

@Get(':id')
async findById(@Param('id') id: string): Promise<Todo> {
return this.todoService.findById(parseInt(id, 10));
}

@Post()
async create(@Body() todo: Todo): Promise<Todo> {
return this.todoService.create(todo);
}

@Put(':id')
async update(@Param('id') id: string, @Body() todo: Todo): Promise<Todo> {
return this.todoService.update(parseInt(id, 10), todo);
}

@Delete(':id')
async delete(@Param('id') id: string): Promise<void> {
return this.todoService.delete(parseInt(id, 10));
}
}

That’s it! You can now start the NestJS server using npm run start:dev and test the API using a tool like Postman or curl.

In my upcoming blog post, I will be taking a closer look at TypeORM and demonstrating how to extend the Todo application to incorporate one-to-many relationships. This is an exciting opportunity to explore the full potential of TypeORM, and I believe that readers will find it both interesting and informative. So be sure to keep an eye out for it!

If you find this information useful, please consider supporting and following me for further updates.🙂

--

--

Brian

Software engineer interested in full stack, Golang, JavaScript, Python, Node.js, React, Nest.js & Next.js. Sharing knowledge through blogs. Follow for updates!