Resizing images in a web application is a common practice for several important reasons:
Performance Optimization: Large images can significantly slow down web page load times. Resizing images to appropriate dimensions and file sizes help improve your web applications performance and allows for a better user experience.
Bandwidth Conservation: Sending large images over the internet consumes bandwidth, which can be costly for both users and the website owner, especially in cases of limited data plans or hosting expenses. Resizing images reduces the amount of data transferred.
Storage Efficiency: Storing large images takes up more server storage space. By resizing images to standard dimensions or using compression, you can reduce storage costs.
Consistent UI/UX: When users upload images of various sizes and dimensions, it can lead to layout issues or distorted visuals on the web page. Resizing images to fit specific design requirements helps maintain consistency.
Mobile Optimization: Mobile devices often have limited screen space and slower network connections. Resizing images for mobile devices ensures that users on smartphones and tablets receive appropriately sized images, improving load times and user experience on these devices.
Responsive Design: For responsive web design, where a website adapts to different screen sizes, resizing images for various breakpoints ensures that the website remains visually appealing on all devices.
So, as you can see there are many reasons why we should resize and optimize user uploaded images. With that said, let’s get started!
Step 1: Install Nest.js CLI
npm i -g @nestjs/cliStep 2: Install type definitions for the Multer.
npm i -D @types/multerwhat is Multer …
“Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.”
Step 3: Install Sharp for image processing.
npm i sharpwhat is Sharp …
“The typical use case for this high speed Node.js module is to convert large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
As well as image resizing, operations such as rotation, extraction, compositing and gamma correction are available.”
Step 4: Register the Multer Module in our app.module.ts
/* App.module.ts*/
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MulterModule } from '@nestjs/platform-express';
@Module({
imports: [MulterModule.register()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Step 5: Create a pipes folder in the src directory and create an image.pipe.ts (this is where all the magic will happen)
Your folder structure should look something like the below:
.
├── dist
├── node_modules
├── public/
│ └── images
├── src/
│ ├── pipes/
│ │ └── image.pipe.ts
│ ├── app.controller.ts
│ ├── app.service.ts
│ ├── app.module.ts
│ └── main.ts
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json
Step 6: Create our “upload” endpoint in the app.controller.ts
/* App.controller.ts*/
import {
Controller,
Post,
UseInterceptors,
UploadedFile,
} from '@nestjs/common';
import { AppService } from './app.service';
import { FileInterceptor } from '@nestjs/platform-express';
// Custom Pipe
import { ImagePipe } from './pipes/image.pipe';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('upload')
@UseInterceptors(FileInterceptor('image'))
async uploadFile(@UploadedFile(ImagePipe) file: Express.Multer.File) {
console.log('Your file has been saved to disk!');
return file;
}
}
Now let’s go over what is happening in the code snippet above:
we have created an AppController class which is a fundamental building block in Nest.js for handling HTTP requests and defining API endpoints.
In summary, this code defines a Nest.js controller with an endpoint for handling file uploads. It uses decorators to configure how the endpoint behaves, including specifying the middleware (FileInterceptor) for handling file uploads and applying a custom pipe (ImagePipe) to process the uploaded file before it reaches the uploadFile method.
Step 7: Now it’s time to create our custom ImagePipe which will implement the PipeTransform interface.
Before moving on it is important to understand what Pipes actually are in the first place and what purpose they actually serve.
A pipe is a class annotated with the @Injectable() decorator, which implements the PipeTransform interface.
pipes operate on the arguments being processed by a controller route handler. Nest interposes a pipe just before a method is invoked, and the pipe receives the arguments destined for the method and operates on them. Any transformation or validation operation takes place at that time, after which the route handler is invoked with any (potentially) transformed arguments.
/* transform.pipe.ts*/
import { Injectable, PipeTransform } from '@nestjs/common';
import { accessSync } from 'node:fs';
import { parse, join } from 'path';
import * as sharp from 'sharp';
@Injectable()
export class ImagePipe
implements PipeTransform<Express.Multer.File, Promise<any>>
{
async transform(image: Express.Multer.File): Promise<any> {
const pathToSave = 'public/images';
try {
accessSync(pathToSave);
const imageType = image.mimetype.split('/')[1];
const originalName = parse(image.originalname).name;
const filename = Date.now() + '-' + originalName + imageType;
// Where the magic happens
await sharp(image.buffer)
.resize({
width: 200,
height: 200,
fit: 'fill',
})
.toFile(join(pathToSave, filename));
// Where the magic happens
return filename;
} catch (err) {
console.error('Error', err);
}
}
}
Now let’s breakdown what is happening in the code above:
ImportsIn summary, this ImagePipe class is designed to be used as a middleware in a Nest.js application to process and resize uploaded images and save them to a specific directory. It performs error handling and returns the generated filename if the processing is successful.
Now using Postman we can send a Post request by form-data and including “image” as a key and uploading an image from our local computer.
By clicking on send our API should now return the saved filename as well as well as having saving the image to our public/images directory.