Database Integration with TypeORM
Connecting to a database (e.g., PostgreSQL, MySQL) using TypeORM. Defining entities, repositories, and performing CRUD operations.
NestJS CRUD Operations with TypeORM
Understanding CRUD Operations
CRUD stands for Create, Read, Update, and Delete. These are the fundamental operations for managing persistent data in any application. In the context of NestJS, we use TypeORM to interact with databases and implement these operations.
- Create: Adding new data entries to the database.
- Read: Retrieving existing data entries from the database.
- Update: Modifying existing data entries in the database.
- Delete: Removing data entries from the database.
Implementing CRUD with TypeORM in NestJS
This section demonstrates how to implement CRUD operations using TypeORM repositories within NestJS services. This will ensure our application properly persists and retrieves data.
Prerequisites:
- A NestJS project set up.
- TypeORM installed and configured (including database connection details).
- An entity defined (e.g., a
User
entity with fields likeid
,name
,email
). - A repository for the entity created (e.g.,
UserRepository
).
Example: User Entity and Repository
First, let's define a simple User
entity:
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}
Then, create a repository for it:
// user.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';
@EntityRepository(User)
export class UserRepository extends Repository<User> {}
Create (POST)
The create
operation adds a new user to the database. The service method injects the UserRepository
and uses its create
and save
methods.
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
) {}
async createUser(name: string, email: string): Promise<User> {
const user = this.userRepository.create({ name, email });
return this.userRepository.save(user);
}
}
// user.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
async createUser(@Body() body: { name: string; email: string }) {
return this.userService.createUser(body.name, body.email);
}
}
Explanation:
- The
createUser
method in the service takes thename
andemail
as input. - It uses the
userRepository.create()
method to create a newUser
entity. - The
userRepository.save()
method persists the new user to the database. - The controller receives the request body, which contains name and email. Then it calls the userService.createUser() to persist the data.
Read (GET)
The read
operation retrieves existing users from the database. We can read either one, or all users.
// user.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
) {}
async getAllUsers(): Promise<User[]> {
return this.userRepository.find();
}
async getUserById(id: number): Promise<User> {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException(`User with ID "${id}" not found`);
}
return user;
}
}
// user.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async getAllUsers() {
return this.userService.getAllUsers();
}
@Get(':id')
async getUserById(@Param('id') id: string) { // Note: id is a string from params
return this.userService.getUserById(parseInt(id, 10)); // convert it to a number
}
}
Explanation:
- The
getAllUsers
method in the service usesuserRepository.find()
to retrieve all users. - The
getUserById
method usesuserRepository.findOne(id)
to retrieve a specific user by ID. Error handling (NotFoundException
) is used to handle scenarios where the user is not found. - The controller methods handle the routing and calls the respective service methods. The param from the path needs to be parsed into an integer.
Update (PUT/PATCH)
The update
operation modifies an existing user in the database.
// user.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
) {}
async updateUser(id: number, name?: string, email?: string): Promise<User> {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException(`User with ID "${id}" not found`);
}
// Optionally update only the fields that are provided in the request.
if (name) {
user.name = name;
}
if (email) {
user.email = email;
}
return this.userRepository.save(user);
}
}
// user.controller.ts
import { Controller, Put, Param, Body } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Put(':id')
async updateUser(
@Param('id') id: string,
@Body() body: { name?: string; email?: string },
) {
return this.userService.updateUser(parseInt(id, 10), body.name, body.email);
}
}
Explanation:
- The
updateUser
method in the service first finds the user by ID usinguserRepository.findOne(id)
. - If the user is not found, a
NotFoundException
is thrown. - Then, it updates the user's properties (
name
,email
) with the new values provided in the request body, only if they are provided. This allows for partial updates. - Finally, it saves the updated user to the database using
userRepository.save(user)
. - The Controller method handles the route and parses the parameters before calling the update method.
Delete (DELETE)
The delete
operation removes a user from the database.
// user.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
) {}
async deleteUser(id: number): Promise<void> {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException(`User with ID "${id}" not found`);
}
await this.userRepository.remove(user); // or userRepository.delete(id);
}
}
// user.controller.ts
import { Controller, Delete, Param, HttpCode, HttpStatus } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT) // 204 No Content - resource deleted
async deleteUser(@Param('id') id: string): Promise<void> {
await this.userService.deleteUser(parseInt(id, 10));
}
}
Explanation:
- The
deleteUser
method in the service first finds the user by ID. - If the user is not found, a
NotFoundException
is thrown. - Then, it removes the user from the database using
userRepository.remove(user)
oruserRepository.delete(id)
. Theremove
method requires fetching the entity first. Thedelete(id)
method directly deletes without fetching. - The Controller's delete method calls the delete method of the service and sends a 204 No Content status back.
Conclusion
This example demonstrates how to implement CRUD operations with TypeORM in NestJS. By using TypeORM repositories, you can easily interact with your database and perform common data management tasks. Remember to adjust the code to fit your specific entity and application requirements. Always handle errors appropriately and consider implementing input validation to ensure data integrity. Consider using DTOs (Data Transfer Objects) to structure your request and response data.