A basic NestJS application, created as part of the Advanced Web Development Bootcamp challenges.
663
This project is a part of the challenge from "Advanced Web Development" Bootcamp, where we focus on building a NestJS application that serves as a book library API corresponding to the OpenAPI specification. The API allows users to perform CRUD operations on books, including creating, reading, updating, and deleting book records.
$ npm install
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the deployment documentation for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out Mau, our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
$ npm install -g @nestjs/mau
$ mau deploy
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
Check out a few resources that may come in handy when working with NestJS:
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.
Nest is MIT licensed.
Use the terminal to navigate to your project directory and run the following command to initialize a new NestJS project:
npx @nestjs/cli new nestjs-ci
cd nestjs-ci
npm install
Create a .gitignore file to exclude node_modules and other unnecessary files from being tracked by Git. You can use the following command:
touch .gitignore
Then add the following lines to your .gitignore file:
# .gitignore
node_modules
dist
Then, run the following commands to initialize Git and make your first commit:
git init
git add .
git commit -m "Initial commit"
Install the GitHub CLI if you haven't already. You can find installation instructions here.
Authorize the GitHub CLI with your GitHub account by running:
gh auth login
Then, create a new repository on GitHub using the following command:
gh repo create nestjs-ci --public --source=. --remote=origin
Create a new directory called .github/workflows in your project root and create a file named ci.yml inside it:
mkdir -p .github/workflows
touch .github/workflows/ci.yml
Then, add the following content to the ci.yml file:
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
This workflow will run on every push or pull request to the main branch. It checks out the code, installs Node.js, installs dependencies using npm ci, and runs tests.
For npm ci command It's important to ensure that your have package-lock.json committed to your repository, as npm ci relies on it to install the exact versions of dependencies specified in the lock file.
Add the .github/workflows/ci.yml file to your Git repository, commit the changes, and push them to GitHub:
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push origin main
This will trigger the GitHub Actions workflow you just created.
Go to your GitHub repository and navigate to the "Actions" tab. You should see the workflow running. If everything is set up correctly, it will pass the tests and show a green checkmark.
To generate the CRUD (Create, Read, Update, Delete) operations for the books, you can use the NestJS CLI to generate a new resource:
npx @nestjs/cli generate resource books
This command will create a new books module, controller, and service in your project.
To add OpenAPI support to your NestJS application, you can use the @nestjs/swagger package. Install it by running:
npm install @nestjs/swagger
Then, update your main.ts file to set up Swagger:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Book Library API')
.setDescription('A simple API to manage a collection of books')
.setVersion('1.0.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
This will set up Swagger documentation for your API at the /api/docs endpoint.
To view the Swagger UI, start your NestJS application npm start and navigate to http://localhost:3000/api in your web browser.
books resource corresponding to the OpenAPI specificationTo implement the OpenAPI specification for the books resource, you need to have 3 DTOs: create-book.dto.ts, update-book.dto.ts and response-book.dto.ts.
In this step, we will create the DTOs and use decorators for validation, transformation and documentation.
npm install @nestjs/class-validator @nestjs/class-transformer @nestjs/swagger
In the create book DTO, you will define the properties required to create a new book.
Update a file named create-book.dto.ts in the src/books/dto directory:
import { IsNotEmpty, IsInt, IsString } from '@nestjs/class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateBookDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ example: 'The Great Gatsby' })
title: string;
@IsNotEmpty()
@IsString()
@ApiProperty({ example: 'F. Scott Fitzgerald' })
author: string;
@IsNotEmpty()
@IsInt()
@ApiProperty({ example: '1925' })
publishedYear: number;
}
Used decorators:
@IsNotEmpty(): Ensures that the field is not empty.@IsString(): Validates that the field is a string.@IsInt(): Validates that the field is an integer.@ApiProperty(): Provides metadata for Swagger documentation, including an example value.The update book DTO will have the same properties but will allow partial updates.
The response book DTO will define the structure of the book object returned by the API.
Create a file named response-book.dto.ts in the src/books/dto directory:
import { Expose } from '@nestjs/class-transformer';
import { IsInt, IsString, IsUUID } from '@nestjs/class-validator';
export class ResponseBookDto {
@Expose()
@IsUUID()
id: string;
@Expose()
@IsString()
title: string;
@Expose()
@IsString()
author: string;
@Expose()
@IsInt()
publishedYear: number;
}
Explanation of decorators used:
@Expose(): Indicates that the property should be included in the serialized output.@IsUUID(): Validates that the field is a valid UUID.@IsString(): Validates that the field is a string.@IsInt(): Validates that the field is an integer.@ApiProperty(): Provides metadata for Swagger documentation, including an example value.There are two main parts to enable validation and transformation in your NestJS application: incoming requests and outgoing responses.
To enable validation and transformation in your NestJS application for incoming requests, you need to set up global pipes in your main.ts file:
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({
transform: true, // Enable transformation of payloads
whitelist: true, // Strip properties that are not in the DTO
forbidNonWhitelisted: true, // Throw an error if non-whitelisted properties are present
}),
);
await app.listen(3000);
}
bootstrap();
Explanation of the options used in ValidationPipe:
transform: true: Automatically transforms the payload into the DTO class instance.whitelist: true: Strips properties that are not defined in the DTO.forbidNonWhitelisted: true: Throws an error if non-whitelisted properties are present.For the outgoing response the NestJS don't apply the validation and transformation by automatically, so you need to use to use plainToClass function from @nestjs/class-transformer in the controller where you return the response.
import { plainToClass } from '@nestjs/class-transformer';
import { ResponseBookDto } from './dto/response-book.dto';
@Get(':id')
async findOne(@Param('id') id: string): Promise<ResponseBookDto> {
const book = await this.booksService.findOne(id);
return plainToClass(ResponseBookDto, book); // Transform the book object to ResponseBookDto.
}
Explanation of the plainToClass function:
plainToClass(ResponseBookDto, book): Converts the plain JavaScript object book into an instance of the ResponseBookDto class, applying any transformation and validation defined in the DTO.Update the books.controller.ts file in the src/books directory to implement the CRUD operations for the books resource. Here is an example of how you can implement the controller.
import {
ApiOkResponse,
ApiNotFoundResponse,
ApiOperation,
} from '@nestjs/swagger';
import { ResponseBookDto } from './dto/response-book.dto';
import { UUID } from 'node:crypto';
import { ParseUUIDPipe } from '@nestjs/common';
import { plainToClass } from '@nestjs/class-transformer';
@Controller('books')
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Get()
@ApiOperation({ summary: 'List all books' })
@ApiOkResponse({
description: 'A list of books',
type: ResponseBookDto,
isArray: true,
})
async findAll(): Promise<ResponseBookDto[]> {
const books = await this.booksService.findAll();
return plainToClass(ResponseBookDto, books);
}
@Get(':id')
@ApiOperation({ summary: 'Get a book by ID' })
@ApiOkResponse({
description: 'A single book',
type: ResponseBookDto,
})
@ApiNotFoundResponse({ description: 'Book not found' })
async findOne(
@Param('id', ParseUUIDPipe) id: UUID,
): Promise<ResponseBookDto> {
const book = await this.booksService.findOne(id);
if (!book) throw new NotFoundException('Book not found');
return plainToClass(ResponseBookDto, book);
}
}
Use the decorators from @nestjs/swagger like @ApiOperation, @ApiOkResponse, and @ApiNotFoundResponse to document the API endpoints.
Use the ParseUUIDPipe from @nestjs/common to validate the incoming request parameters, such as the book ID. This ensures that the ID is a valid UUID before processing the request.
Use the plainToClass function from @nestjs/class-transformer to transform the response object to the DTO. This ensures that the response adheres to the structure defined in the DTO and applies any necessary transformations.
Use async/await for asynchronous operations to handle the database calls in a non-blocking manner.
Note that the return type of asynchronous function is Promise<SomeType>, regardless of the actual type returned by the function.
Use the ResponseBookDto as the return type for the findOne, update, and remove methods to ensure that the response adheres to the structure defined in the DTO.
Use the NotFoundException from @nestjs/common to handle cases where a book is not found. This will return a 404 status code and a descriptive error message.
To dockerize the NestJS application, you need to create a Dockerfile in the root directory of your project. Here is a basic example of a Dockerfile for a NestJS application:
This Dockerfile does the following:
/app.package.json and package-lock.json files to the working directory.To build the Docker image, run the following command in the root directory of your project:
docker build -t my-nestjs-app .
To run the Docker container, use the following command:
docker run -p 3000:3000 my-nestjs-app
This will map port 3000 of the container to port 3000 on your host machine, allowing you to access the application at http://localhost:3000.
To push your Docker image to Docker Hub, you need to create an account on Docker Hub if you don't have one already. Follow these steps:
docker login
You will be prompted to enter your Docker Hub username and password.
To tag your Docker image with your Docker Hub username, use the following command:
docker tag my-nestjs-app <your-dockerhub-username>/my-nestjs-app
To push your Docker image to Docker Hub, use the following command:
docker push <your-dockerhub-username>/my-nestjs-app
Make sure to replace my-nestjs-app with the name of your Docker image. If you want to push it to a specific repository, you can use the following format:
docker push <your-dockerhub-username>/my-nestjs-app
To run the Docker container from Docker Hub, use the following command:
docker run -p 3000:3000 <your-dockerhub-username>/my-nestjs-app
This will map port 3000 of the container to port 3000 on your host machine, allowing you to access the application at http://localhost:3000.
Docker Hub repository names should be in lowercase letters. If you try to push an image with uppercase letters in the repository name, you will get an error. Make sure to use only lowercase letters when naming your Docker Hub repository.
Example with error (uppercase letters in repository name):
$ docker images | grep awd
CodeShip404/awd-nestjs latest 5484a00ce1ca 12 minutes ago 434MB
$ docker push CodeShip404/awd-nestjs:latest
The push refers to repository [CodeShip404/awd-nestjs]
Get "https://CodeShip404/v2/": dial tcp: lookup CodeShip404 on 127.0.0.53:53: server misbehaving
Example with correct (all lowercase) repository name:
$ docker tag CodeShip404/awd-nestjs:latest codeship404/awd-nestjs:latest
$ docker push codeship404/awd-nestjs:latest
The push refers to repository [docker.io/codeship404/awd-nestjs]
... # (push proceeds successfully)
As shown above, using uppercase letters in the repository name causes an error. Always use lowercase letters for Docker Hub repository names to avoid this issue.
The COPY command in Dockerfile is used to copy files from the host machine into the Docker image.
The COPY command is not equivalent to the cp -r command in the terminal.
The COPY src . will copy the contents of the src directory into the current working directory in the Docker image
whereas cp -r src . will copy the entire src directory into the current working directory in the terminal
For example, if you have the following directory structure:
some-project/
├── Dockerfile
├── package.json
└── src/
├── app.module.ts
└── main.ts
You can use the following Dockerfile to copy the contents of the src directory into the /app directory in the Docker image:
WORKDIR /app
COPY package*.json ./
COPY src ./
This will copy the contents of the src directory directly into the /app directory in the Docker image, resulting in:
/app
├── app.module.ts
├── main.ts
├── package.json
└── package-lock.json
but possibly you want to have the src directory inside the /app directory, so that the structure looks like this:
/app
├── package.json
├── package-lock.json
└── src/
├── app.module.ts
└── main.ts
You must copy to ./src to copy the contents of the src directory into the /app/src directory in the Docker image:
WORKDIR /app
COPY package*.json ./
COPY src ./src
This will copy the contents of the src directory into the /app/src directory in the Docker image, resulting in:
├── app.module.ts
└── main.ts
.dockerignore to exclude unnecessary files from the build context.Content type
Image
Digest
sha256:f1b9814a9…
Size
94.8 MB
Last updated
about 1 year ago
docker pull codeship404/awd-nestjs