Image Processing Pipelines with Sharp
Resize, transcode, and validate uploaded images in a background-safe processing pipeline.
Why Sharp for Image Pipelines
When users upload images to your NestJS API, you rarely want to store the raw file as-is. A 12-megapixel phone photo can be 8 MB of JPEG that you serve to mobile clients on slow networks.
Sharp is the de-facto Node.js image processing library. It wraps libvips, a low-level C library that processes images in a streaming, low-memory way.
- Fast: 4-5x faster than ImageMagick bindings.
- Memory-safe: libvips streams pixels in chunks instead of decoding the whole image into RAM.
- Format-rich: reads/writes JPEG, PNG, WebP, AVIF, GIF, TIFF, SVG.
In this lesson you will build a resize-transcode-validate pipeline that is safe to run on uploaded, untrusted images.
The Sharp Pipeline Mental Model
A Sharp instance is a lazy pipeline. Method calls like .resize() and .webp() only queue operations. Nothing is decoded or encoded until you call a terminal method like .toBuffer() or .toFile().
Because it is lazy, you can build the pipeline once and execute it multiple times. The input can be a file path, a Buffer, or a readable stream.
import sharp from 'sharp';
async function run(): Promise<void> {
// 1x1 red pixel PNG, base64-encoded
const pngBase64 =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
const input = Buffer.from(pngBase64, 'base64');
// Pipeline: queue resize + WebP, then execute with toBuffer()
const output = await sharp(input)
.resize(64, 64)
.webp({ quality: 80 })
.toBuffer();
console.log('Output bytes:', output.length);
console.log('Is WebP:', output.slice(8, 12).toString() === 'WEBP');
}
run().catch((err) => console.error(err));All lessons in this course
- Multipart Uploads with Multer Interceptors
- Streaming Large Responses with StreamableFile
- Direct-to-S3 Uploads with Presigned URLs
- Image Processing Pipelines with Sharp