Additional Nestjs Concepts
File Upload
Step 1: Install Multer Types
npm install @types/multer
This is a package that provides TypeScript type definitions for the multer package, which is a middleware for handling multipart/form-data (typically used for uploading files) in Node.js.
When working with TypeScript, it's important to have accurate type definitions for third-party libraries like multer. These type definitions help you write more reliable and type-safe code by providing information about the functions, objects, and properties that the library exposes.
Step 2 Create upload route handler
//app.controller.ts
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import {
HttpStatus,
ParseFilePipeBuilder,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
console.log(file);
}
@UseInterceptors(FileInterceptor('file')): This decorator indicates that the method should use the FileInterceptor interceptor to handle file uploads.
The string 'file' inside the FileInterceptor decorator refers to the field name in the request payload that contains the uploaded file
This interceptor automatically processes the uploaded file and makes it accessible in the @UploadedFile() decorator.
uploadFile(@UploadedFile() file: Express.Multer.File): This parameter decorator retrieves the uploaded file that was processed by the FileInterceptor
The @UploadedFile() decorator extracts the uploaded file from the request and assigns it to the file parameter.
Step 3: Upload file with Validations
@Post('upload-png')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './upload/files',
filename: (req, file, cb) => {
cb(null, file.originalname);
},
}),
}),
)
uploadFileWithValidation(
@UploadedFile(
new ParseFilePipeBuilder()
.addFileTypeValidator({
fileType: 'png',
})
// .addMaxSizeValidator({
// maxSize: 70706,
// })
.build({
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
}),
)
file: Express.Multer.File,
) {
console.log(file);
return {
messge: 'file uploaded successfully!',
};
}
The interceptor is configured with a diskStorage option, specifying that the uploaded files will be stored on the local disk. The destination option specifies the directory where the uploaded files will be saved, and the filename option determines the name of the saved file (in this case, it retains the original name)
ParseFilePipeBuilder: This is a custom utility that creates a pipe for validating uploaded files. In this code, it's used to add a validation check to ensure that the uploaded file is of type PNG
Custom Decorator
What are Custom Dectorators?
Custom decorators in Nest.js are user-defined annotations that allow you to add metadata to classes, methods, properties, or parameters in your application. These decorators are a powerful feature of TypeScript and enable you to extend the functionality of Nest.js by creating reusable and structured code constructs.
Here's why you might want to use custom decorators in Nest.js:
Modularity and Reusability: Custom decorators enable you to encapsulate specific functionality or behavior into reusable modules. By creating decorators, you can define a set of actions that can be easily applied to different parts of your application, promoting modularity and reducing code duplication.
Code Organization: Nest.js applications can become complex as they grow. Custom decorators allow you to keep related code together and organize it in a structured manner. This enhances code readability and maintainability.
Cross-Cutting Concerns: Cross-cutting concerns, such as logging, authentication, authorization, and validation, often need to be applied to multiple parts of your application. Custom decorators provide a clean way to inject these concerns into your codebase without cluttering your business logic.
Aspect-Oriented Programming: Decorators enable a programming paradigm called aspect-oriented programming (AOP), where you can separate concerns that cross multiple parts of your application. This separation makes your codebase more modular and easier to understand.
Metadata and Reflection: Decorators provide a way to add metadata to your classes, methods, or properties. This metadata can be introspected and used for various purposes, such as generating documentation, enforcing policies, or performing transformations.
Extend Nest.js Functionality: Nest.js itself uses decorators extensively to add features like routing, dependency injection, middleware, and more. By creating custom decorators, you can extend the core functionality of Nest.js to suit your specific needs.
Domain-Specific Language: Decorators allow you to create a domain-specific language (DSL) that expresses the intent of certain operations in a clear and concise way, making your codebase more expressive and easier to understand.
Third-Party Libraries: You can use custom decorators to integrate with third-party libraries, frameworks, or tools. For example, you could create a decorator that integrates with a logging library or an authentication module.
In summary, custom decorators in Nest.js provide a mechanism to add behavior, metadata, or cross-cutting concerns to your codebase in a modular and reusable way. They enhance code organization, promote best practices, and allow you to extend and customize the behavior of your application beyond what the framework provides out of the box.
Step 1: Create Custom Decorator
//user.decorator.ts
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
request.user = { id: 1, name: "Haider Ali" };
return request.user;
}
);
(data: unknown, ctx: ExecutionContext) => { ... }: This is the decorator factory function. It takes two parameters:
data: This parameter allows you to pass additional data to the decorator. In your code, you're not using this parameter, so it's of type unknown.
ctx: ExecutionContext: This parameter provides context information about the current request. It's used to access the request object.
const request = ctx.switchToHttp().getRequest();: This line retrieves the request object from the ExecutionContext. The switchToHttp() method returns an HttpArgumentsHost instance that provides access to the request and response objects.
Step 2: Create User Entity
export class UserEntity {
id: number;
name: string;
}
Step 3: Apply User Decorator
import { User } from './user.decorator';
import { UserEntity } from './user.entity';
@Get('/user/:id')
findOne(
@User()
user: UserEntity,
) {
console.log(user);
return user;
}
Running CRON Task
Step 1: Install Packages
Many times, we need to run an arbitrary piece of code based on some schedule. The schedule can be a fixed time, a recurring interval or after a specific timeout.
Let's install dependencies
npm install --save @nestjs/schedule
npm install --save-dev @types/cron
Step 2: Register ScheduleModule in AppModule
import { ScheduleModule } from '@nestjs/schedule';
imports: [ScheduleModule.forRoot()],
Step 3: Create TaskService and define CRON Job
nest g service Task
import { Injectable, Logger } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
@Injectable()
export class TaskService {
private readonly logger = new Logger(TaskService.name);
@Cron("0 * * * * *")
myCronTask() {
this.logger.debug("Cron Task Called");
}
}
* * * * * *
| | | | | |
| | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
seconds (optional)
Examples
* * * * * * every second
45 * * * * * every minute, on the 45th second
0 10 * * * * every hour, at the start of the 10th minute
0 */30 9-17 * * * every 30 minutes between 9am and 5pm
0 30 11 * * 1-5 Monday to Friday at 11:30am
Cookies
What are Cookies?
Cookies are small pieces of data that a server sends to a user's web browser while the user is browsing a website. These cookies are stored on the user's device and are used to store information about the user's interactions with the website. Cookies are an essential part of web technology and play a crucial role in providing a personalized and seamless browsing experience. Here's why cookies are important:
Session Management: Cookies are often used to manage user sessions. When a user logs in to a website, a session cookie is created, which allows the server to recognize the user and maintain their authenticated state as they navigate different pages on the site.
Personalization: Cookies can store user preferences and settings, allowing websites to customize the user experience based on their previous interactions. This could include remembering language preferences, display settings, and more.
Shopping Carts and E-Commerce: E-commerce websites use cookies to maintain shopping carts. Items added to a cart are stored in a cookie, allowing the user to continue shopping and complete their purchase later.
Tracking and Analytics: Cookies are often used to gather data about user behavior, such as which pages they visit, how long they stay on a page, and what actions they take. This data is then used for analytics and to improve the website's performance and user experience.
Authentication and Security: Cookies can help with authentication and security by storing tokens or other information that verifies a user's identity. For example, a website might use cookies to remember that a user is logged in, so they don't have to log in again on every page.
Remember Me Functionality: Many websites offer a "Remember Me" option when logging in. This sets a persistent cookie that allows the user to stay logged in even after closing and reopening their browser.
Tracking User Activity: Cookies can be used to track a user's activity across different websites. This is often used for targeted advertising and marketing.
Load Balancing: In some cases, cookies are used to help distribute website traffic across multiple servers, ensuring a more even load distribution.
State Management: Cookies are often used to manage state information in web applications. For example, they can be used to remember where a user was in a multi-step process.
Caching: Cookies can be used to cache certain information on the user's device, reducing the need to repeatedly request the same data from the server.
While cookies offer many benefits, it's important to note that there are also concerns related to privacy and security. Some users may disable or delete cookies to protect their privacy, and there are regulations in place, such as the General Data Protection Regulation (GDPR), that govern how cookies and user data can be used.
In recent years, there has been an increased focus on alternative solutions like server-side sessions, local storage, and newer web technologies like Web Storage API and IndexedDB. These options provide alternatives to traditional cookies for storing data on the user's device.
Step 1: Install Packages
npm install cookie-parser
npm install -D @types/cookie-parser
Step 2: Register Cookie Parser
//main.ts
import * as cookieParser from "cookie-parser";
app.use(cookieParser());
Step 3: Set Cookies
//app.controller.ts
import {
Req,
Res,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Get('set-cookie')
setCookie(
@Res({ passthrough: true })
response: Response,
) {
response.cookie('Cookie token Name', 'encrypted cookie string');
response.send('Cookie Saved Successfully');
}
@Get('get-cookie')
finndAll(@Req() req: Request) {
console.log(req.cookies);
return req.cookies;
}
Step 4: Test
When you run the application you have to send the setCookies request first from your browser. The cookie will be saved in your Google Chrome Browser. You have to open chrome dev tools and find the Application tab. You can find your cookies there.
Queues
What are Queues in Nest.js?
In Nest.js, queues refer to the concept of handling tasks asynchronously using a queuing system. Queues are a way to manage and process tasks in the background without blocking the main application's execution. This is particularly useful for tasks that are time-consuming, resource-intensive, or don't need to be executed immediately as part of a request-response cycle.
A common use case for queues is to process tasks like sending emails, generating reports, processing images, or any other task that can be offloaded from the main application flow.
Here's a high-level overview of how queues work in Nest.js:
Task Generation: In your application, you identify tasks that can be processed asynchronously. For example, let's consider sending an email after a user registers. Instead of sending the email directly within the registration endpoint, you can push the task of sending the email to a queue.
Queue Library Integration: You integrate a queue library of your choice (e.g., Bull) into your Nest.js application. This involves installing the library, configuring it, and creating a queue instance.
Enqueuing Tasks: When you want to perform a task asynchronously, you enqueue it in the queue. For our example, after a user registers, you enqueue a task to send the registration email.
Worker Process: You create one or more worker processes that continuously monitor the queue for tasks. When a task is available in the queue, a worker picks it up and processes it. In our example, the worker would send the registration email.
Background Execution: The task is executed in the background, separate from the main application thread. This ensures that the main application remains responsive and doesn't get blocked by time-consuming tasks.
Step 1: Install Dependencies
npm install @nestjs/bull
npm install bull
@nestjs/bull is a Nest.js module that provides integration with the Bull queue library, which is built on top of Redis. In other words, when you use @nestjs/bull for implementing queues in your Nest.js application, you are actually using Redis under the hood.
Step 2: Creating Audio Module
nest g module audio && nest g controller audio
Step 3: Setup Redis with docker-compose
You have to create a new file with docker-compose.yml in the root directory. Make sure you have installed docker on your machine
version: "3"
services:
redis:
image: redis:alpine
ports:
- 6379:6379
Let's start the redis service by opening the terminal and run docker-compose up
Step 4: Register BullModule
//app.module.ts
imports: [
BullModule.forRoot({
redis: {
host: "localhost",
port: 6379,
},
}),
];
You have to register the BullModule in the AppModule
Step 5: Register BullModule Queue
//audio.module.ts
imports: [
BullModule.registerQueue({
name: 'audio-queue',
}),
],
registerQueue({...}): This method is used to register a queue within your application. It takes an options object as an argument to configure the queue.
name: 'audio-queue': This is the name property within the options object. It specifies the name of the queue you want to create. In this case, the queue will be named "audio-queue".
Step 6: Creating audio convertor endpoint
import { InjectQueue } from "@nestjs/bull";
import { Controller, Post } from "@nestjs/common";
import { Queue } from "bull";
@Controller("audio")
export class AudioController {
constructor(
@InjectQueue("audio-queue")
private readonly audioQueue: Queue
) {}
/**
* Let's imagine we would like to convert .wav file into .mp3
*/
@Post("convert")
async convert() {
await this.audioQueue.add("convert", {
file: "sample.wav",
});
}
}
Step 7: Implement Audio Processor
import { Process, Processor } from "@nestjs/bull";
import { Logger } from "@nestjs/common";
import { Job } from "bull";
@Processor("audio-queue")
export class AudioProcessor {
private logger = new Logger(AudioProcessor.name);
@Process("convert")
handleConvert(job: Job) {
this.logger.debug("start converting wav file to mp3");
this.logger.debug(job.data);
this.logger.debug("file converted successfully");
}
}
@Processor('audio-queue'): This decorator specifies that this class is a processor for the "audio-queue". This means that it will handle tasks enqueued in the "audio-queue".
@Process('convert'): This decorator specifies that the handleConvert method should handle tasks with the name "convert". When a task with the name "convert" is enqueued in the "audio-queue", the handleConvert method will be triggered to process it.
handleConvert(job: Job): This is the method that processes tasks with the name "convert". It takes a Job object as its parameter, which contains information about the task and its data
Event Emitter
What is an Event Emitter?
In Nest.js, an event emitter is a mechanism that allows different parts of your application to communicate with each other using an event-driven approach. It's a way to facilitate communication and coordination between different modules, services, components, or classes within your application.
An event emitter works on the principle of publishers and subscribers. The entity that generates an event is called the "publisher," and the entity that listens and responds to the event is called the "subscriber."
Why do we need it?
Practical Use Cases of Event Emitters in Nest.js:
Module Communication: Modules in a Nest.js application are often designed to be independent and self-contained. However, there are scenarios where modules need to communicate with each other. Event emitters provide a way for one module to emit an event and for other modules to react to that event.
Service Interaction: Different services within your application might need to communicate or coordinate their actions. For example, when a user performs an action in one service, it might trigger actions in another service. Event emitters can facilitate this communication without creating direct dependencies between services.
Notification Systems: You can use event emitters to implement a notification system. When an important event occurs, you can emit an event, and subscribers (such as notification services) can react by sending notifications to users or other parts of the system.
Real-time Updates: In real-time applications, you can use event emitters to send updates to connected clients. For example, in a chat application, when a new message is received, the server can emit an event, and all connected clients receive the update instantly.
Plugin System: If your application supports plugins or extensions, event emitters can allow plugins to listen for specific events and modify the behavior of the core application accordingly.
Logging and Monitoring: You can use event emitters to notify a logging or monitoring system about significant events within your application. This can help track and analyze the application's behavior.
Workflow Orchestration: For complex workflows involving multiple steps or processes, event emitters can be used to trigger the next step once the previous step is completed.
Error Handling and Reporting: When an error occurs, you can emit an event that notifies an error handling service. This service can then log the error, notify administrators, or take other appropriate actions.
User Authentication and Authorization: Event emitters can be used to handle user authentication and authorization events. For example, an authentication service could emit an event when a user successfully logs in, and other parts of the application can respond accordingly.
Caching and Data Management: In a cache management system, event emitters can be used to notify the cache to update or clear cached data when relevant changes occur in the application.
Overall, event emitters in Nest.js facilitate loose coupling between different parts of your application, enabling better modularity, scalability, and maintainability. They help achieve separation of concerns and allow different parts of the application to interact without needing to know the details of each other's implementation.
Use Case
We are going to take an example from our previous example let's say we want to send the notification to the user when the .wav file converts successfully into .mp3 format.
You can use EventEmitter to send the notification to the user
Step 1: Install Dependencies
npm install @nestjs/event-emitter
Step 2: Register EventEmitter Module
//app.module.ts
EventEmitterModule.forRoot(),
Step 3: Create AudioConvertedListener
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AudioConvertedEvent } from './events/audio-converted-event';
@Injectable()
export class AudioConvertedListener {
@OnEvent('audio.converted') // We have registered a new event lister with audio.converted name
handleAudioConvertedEvent(event: AudioConvertedEvent) { //We have to create the type for the AudioConvertedEvent
console.log(event);
// Here you can have your EmailService method you can call here
console.log(
'Notification has been sent to user that file is converted successfully,
);
}
}
Step 4: Create AudioConvertedEvent Type
export class AudioConvertedEvent {
file: string;
id: number;
}
You have to created this class in your audio folder.
Make sure you have registered it in your provider
providers: [AudioProcessor, AudioConvertedListener],
Step 5: Emit the Event in AudioProcessor
constructor(private eventEmitter: EventEmitter2) {}
Make sure you have injected the EventEmitter dependency in your AudioProcessor class
handleConvert(job: Job){
//...
this.eventEmitter.emit('audio.converted', job.data);
}
We have to emit the event in the handleConvert method.
Step 6: Run the Application
Now you have to run the application and send the audio convert request from http-client.http
You will see the message Notification has been sent to the user that file is converted successfully. It will also log the event details
Streaming
What is Streaming
Streaming in Nest.js refers to the process of sending or receiving data in small chunks, called "streams," rather than sending or receiving the entire data at once. This concept is based on the Stream API in Node.js and is utilized for more efficient data handling, especially when dealing with large amounts of data, such as files, network requests, or real-time data transmission.
Practical Use Cases of Streaming in Nest.js:
File Uploads and Downloads: When uploading or downloading large files, streaming allows you to process the data in chunks, reducing memory consumption and improving performance. This is particularly useful for handling large media files, backups, or logs.
Real-time Communication: Streaming is essential for real-time communication technologies like WebSockets. Streaming data in real-time ensures that clients receive updates as they happen, which is critical for applications like chat applications, live feeds, or online gaming.
Media Streaming: Applications that involve streaming audio or video content, such as music or video platforms, benefit from the ability to stream data to users' devices progressively. This allows users to start consuming the media before the entire file is downloaded.
API Responses: Streaming can be used to send large responses from APIs, like lists of items, without waiting for the entire response to be generated. This can improve the API's responsiveness and user experience.
Data Transformation: Streaming is used when processing data transformations or conversions, such as reading data from one format, transforming it, and writing it into another format. This can be used in ETL (Extract, Transform, Load) processes or data pipelines.
Reading from Streams: Reading from a stream is useful when processing data from sources that generate data incrementally, such as reading log files or parsing large XML or JSON documents.
Data Aggregation: When dealing with data aggregation or analytics, streaming can be used to process data in smaller chunks, allowing for real-time analysis or reducing memory usage.
Server-Sent Events (SSE): SSE is a technology that enables a server to push real-time updates to a web browser over a single HTTP connection. It uses streaming to send a continuous stream of events to the client.
Batch Processing: In scenarios where data is collected in batches, streaming can be used to process and handle each batch efficiently.
Database Operations: When reading or writing large volumes of data to databases, streaming can help optimize data insertion or extraction processes.
Proxy Servers: Streaming can be used in proxy servers to forward data from one server to another without holding the entire data in memory.
Data Transmission Optimization: Streaming can help optimize data transmission in scenarios with limited bandwidth, ensuring that data is transferred in manageable chunks.
In summary, streaming in Nest.js is a versatile technique used for efficient data handling, especially when dealing with large volumes of data or real-time communication. It enhances application performance, reduces memory consumption, and improves user experience by allowing data to be processed incrementally as it's received or sent.
Step 1: Create a FileController
nest g controller file
Step 2: Download the file
import { Controller, Get, Header, Res, StreamableFile } from '@nestjs/common';
import { Response } from 'express';
import { createReadStream } from 'fs';
import { join } from 'path';
//Download the file
@Get('stream-file')
getFile1(): StreamableFile {
const file = createReadStream(join(process.cwd(), 'package.json'));
return new StreamableFile(file);
}
@Get('stream-file-customize')
getFileCustomizedResponse(@Res({ passthrough: true }) res): StreamableFile {
const file = createReadStream(join(process.cwd(), 'package.json'));
res.set({
'Content-Type': 'application/json',
'Content-Disposition': 'attachment; filename="package.json',
});
return new StreamableFile(file);
}
Session
What is a session?
In Nest.js, sessions refer to the concept of maintaining stateful data between consecutive requests from the same client. A session allows you to store and retrieve user-specific information on the server across multiple HTTP requests, typically using cookies to identify the session.
Practical Uses of Session
User Authentication: Sessions are commonly used for maintaining the authentication state of a user. When a user logs in, a session can be created with their authentication data, and subsequent requests can be authenticated based on the session.
Authorization: Sessions can store authorization-related information, such as user roles and permissions, to determine what actions the user is allowed to perform.
User Preferences: You can use sessions to store user-specific preferences or settings, such as language preference or display settings.
Shopping Carts: E-commerce applications often use sessions to store the contents of a user's shopping cart as they navigate through the site.
User Tracking: Sessions can be used to track user behavior and interactions on a website, helping in analyzing user engagement and improving user experience.
Caching: Sessions can store frequently accessed data, reducing the need to fetch the same data from the database on every request.
Personalization: Websites can customize content based on user behavior stored in sessions, enhancing user experience.
Form Data Persistence: Sessions can temporarily store form data between requests, which is useful in multi-step processes.
Step 1: Install Dependencies
npm install express-session
npm install -D @types/express-session
Step 2 Register Middleware
app.use(
session({
secret: "my-secret",
resave: false,
saveUninitialized: false,
})
);
Step 3: Create Login Route Handler
@Get('login')
loginUser(@Session() session: Record<string, any>) {
session.user = { id: 1, username: 'Jane' };
return 'LoggedIn';
}
Step 4: Profile Route
@Get('profile')
profile(@Session() session: Record<string, any>) {
const user = session.user;
if (user) {
return `Hello, ${user.username}`;
} else {
return 'Not logged in';
}
}
$7 bundle of my best NestJS + backend developer Courses.
More details coming soon.
Get the latest insights from the marketing world.