This series introduces developers to NestJS. Part 2 shows how to set up a SQL Azure database, create endpoints and perform CRUD operations.
This is the second part of the NestJS learning series. Part 1 covered setting up the Nest project.
In this part, we will cover the following topics:
MySQL Azure has a database called nomadcoder. In this article, we will connect to this database and perform CRUD operations on a company table. We will create the book table from the code itself.
To connect to SQL Azure, you’ll need a connection string, which can be found on the database’s overview page.
Click on Connection Strings, then go to the ODBC tab to find the connection string for the Nest API.
To connect to SQL Azure, you must install the ODBC driver on your computer. If it’s not installed, you can download it.
We will use TypeORM to connect to SQL Azure, so install the dependencies listed below in your project.
npm install @nestjs/typeorm
npm install typeorm mssql
To set up TypeORM in the project, update the app.module.ts file below. Include the TypeOrmModule
in the module’s imports array.
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mssql',
host: '<your-sql-azure-server>.database.windows.net',
port: 1433,
username: '<your-username>',
password: '<your-password>',
database: '<your-database>',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
options: {
encrypt: true,
},
}),
],
Update the following values:
We first need to define an entity corresponding to a database table to implement CRUD endpoints. Since Nest CLI doesn’t provide a command to generate entities, we will create them manually.
We will migrate the books array from the previous post to an Azure SQL database. To start, create a new file named book.entity.ts inside the book folder.
When working with TypeORM, the entity file needs to be named with the .entity.ts extension. We can create the Company entity class as shown below:
book.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Book {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
author: string;
}
TypeORM will use the Book
class as an entity. Entities in TypeORM represent tables in a database, and each class instance corresponds to a row in that table.
The @Entity()
decorator marks the class as a database entity, so TypeORM knows to create a table for it. Each property in the class is decorated with either @PrimaryGeneratedColumn()
or @Column()
, which tells TypeORM how to map these properties to columns in the database table.
Once the Book
entity has been added to the project, include it in the imports array of the AppModule as shown below.
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mssql',
host: 'nomadcoder.database.windows.net',
port: 1433,
username:’’,
password: '',
database: 'nomadcoder',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // Disable in production
options: {
encrypt: true,
},
}),
TypeOrmModule.forFeature([Book]),
],
At this point, when you run the application, a book table with no data will be created in the Azure SQL database.
We will implement the logic to insert a book record within the BookService
class. To perform database operations, import the Repository from TypeORM and inject it into the service’s constructor.
import { Book } from './book.entity';
import { Repository } from 'typeorm';
@Injectable()
export class BookService {
constructor(
@InjectRepository(Book)
private readonly bookRepository: Repository<Book>,
) {}
}
To insert a book record, use the create function of the repository class as shown below:
async create(bookData: Partial<Book>): Promise<Book> {
const book = this.bookRepository.create(bookData);
return this.bookRepository.save(book);
}
BookRepository.create(bookData)
is called inside the asynchronous create method. This creates a new Book
entity instance using the provided data.
The following line is bookRepository.save(book)
, which saves the newly created entity to the database and returns the saved Book
object. The save method is asynchronous and returns a promise that resolves to the persisted entity.
In the BookController
, add a POST endpoint to insert a new book.
@Post()
async create(@Body() bookData: Partial<Book>, @Res() res: Response) {
try {
const book = await this.bookService.create(bookData);
return res.status(HttpStatus.CREATED).json(book);
} catch (error) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error creating book' });
}
}
The asynchronous create method accepts two parameters: @Body() bookData: Partial<Book>
and @Res() res: Response
.
The @Body()
decorator extracts and maps the request body to the bookData
. As the bookData
type is partial, the client can send an object with some or all of the Book
properties.
The @Res()
decorator injects the raw response object, allowing manual control over the HTTP response.
Inside the method, a try-catch block handles errors. The code attempts to create a new book by calling bookService.create(bookData)
.
If successful, it sends a response with HTTP status code 201 (Created) and the newly created book as JSON. If an error occurs during the process, the catch block sends a 500 (Internal Server Error) response with an error message.
When you send a POST request to http://localhost:3000/book with a book object in the request body, a new row should be inserted into the book table.
{
"id": 1,
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald"
}
To fetch data from the SQL Azure book table, we will use TypeORM’s repository functions.
async findAll(): Promise<Book[]> {
return this.bookRepository.find();
}
async findOne(id: number): Promise<Book | null> {
return this.bookRepository.findOneBy({ id });
}
We are using TypeORM’s repository pattern methods. The bookRepository.find()
function retrieves all book records from the database, while bookRepository.findOneBy({ id })
fetches a specific book by its ID. If a matching book is found, it’s returned; otherwise, the method returns null.
In the BookController
, add GET endpoints to fetch books or a particular book.
@Get()
async findAll(@Res() res: Response) {
try {
const books = await this.bookService.findAll();
return res.status(HttpStatus.OK).json(books);
} catch (error) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error fetching books' });
}
}
findAll()
method is asynchronous and takes one parameter @Res() res: Response
for custom response handling.bookService.findAll()
function to fetch all books from the database.To fetch a particular book with the ID, add the endoint as below.
@Get(':id')
async findOne(@Param('id') id: string, @Res() res: Response) {
try {
const book = await this.bookService.findOne(Number(id));
if (book) {
return res.status(HttpStatus.OK).json(book);
} else {
return res.status(HttpStatus.NOT_FOUND).json({ message: 'Book not found' });
}
} catch (error) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error fetching book' });
}
}
findOne()
method is asynchronous and takes two parameters: @Param('id') id: string
and @Res() res: Response
.@Param('id')
decorator extracts the id value from the URL and @Res()
for custom response handling. It uses bookService.findOne()
function to fetch a particular book from the database.When you send a GET request to http://localhost:3000/book, you should get all books. And when you send a GET request to http://localhost:3000/book/1, you should get the particular book with ID 1.
To delete data from the book table, we will use TypeORM’s repository functions.
async remove(id: number): Promise<DeleteResult> {
return this.bookRepository.delete(id);
}
We are using the TypeORM bookRepository.delete()
function to delete the record with the given ID from the book table.
In the BookController
, add DELETE endpoints to delete a book with the given Id as shown below:
@Delete(':id')
async remove(@Param('id') id: string, @Res() res: Response) {
try {
const result = await this.bookService.remove(Number(id));
if (result.affected && result.affected > 0) {
return res.status(HttpStatus.NO_CONTENT).send();
} else {
return res.status(HttpStatus.NOT_FOUND).json({ message: 'Book not found' });
}
} catch (error) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error deleting book' });
}
}
remove()
method is asynchronous and takes two parameters: @Param('id') id: string
and @Res() res: Response
.@Param('id')
decorator extracts the id value from the URL and @Res()
for custom response handling. It uses bookService.remove()
function to delete a particular book from the database.To delete a particular book with the ID 1, perform a DELETE operation on the endpoint http://localhost:3000/book/1.
To update data in the book table, we will use TypeORM’s repository function update.
async update(id: number, updateData: Partial<Book>): Promise<Book | null> {
await this.bookRepository.update(id, updateData);
return this.bookRepository.findOneBy({ id });
}
We are using the TypeORM bookRepository.update()
function to update the record with the given ID from the book table, and then fetching that particular updated record.
In the BookController
, add PUT endpoints to update a book with the given ID as shown below:
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateData: Partial<Book>,
@Res() res: Response
) {
try {
const updatedBook = await this.bookService.update(Number(id), updateData);
if (updatedBook) {
return res.status(HttpStatus.OK).json(updatedBook);
} else {
return res.status(HttpStatus.NOT_FOUND).json({ message: 'Book not found' });
}
} catch (error) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error updating book' });
}
}
update()
method is asynchronous and takes three parameters: @Param('id') id: string
, updateData
to update the book and @Res() res: Response
.@Param('id')
decorator extracts the id value from the URL and @Res()
for custom response handling. It uses bookService.update()
function to update a particular book from the database.To update a particular book with the ID 1, perform a PUT operation on the endpoint http://localhost:3000/book/1.
In this part of the multi-part NestJS learning series, you developed an API capable of performing CRUD operations on a SQL Azure table. I hope you found this section helpful—thanks for following along!
Dhananjay Kumar is a well-known trainer and developer evangelist. He is the founder of NomadCoder, a company that focuses on creating job-ready developers through training in technologies such as Angular, Node.js, Python, .NET, Azure, GenAI and more. He is also the founder of ng-India, one of the world’s largest Angular communities. He lives in Gurgaon, India, and is currently writing his second book on Angular. You can reach out to him for training, evangelism and consulting opportunities.