Generate thumbnails asynchronously using Bull + Redis job queue. Create multiple sizes (small 150x150, medium 300x300, large 600x600). Use Sharp for fast image processing. Return job ID immediately to client, notify when ready via webhook or polling. By the end, thumbnails are generated in the background without blocking uploads.
← Back to Module 03 overviewGET /images/:id returns original + all thumbnail URLs.GET /jobs/:jobId shows job status and progress.Install: npm install bull ioredis
Create src/workers/thumbnailWorker.ts:
import Queue from 'bull';
import sharp from 'sharp';
import fs from 'fs/promises';
import path from 'path';
import pool from '../db/pool';
const thumbnailQueue = new Queue('thumbnails', process.env.REDIS_URL);
const SIZES = {
small: { width: 150, height: 150 },
medium: { width: 300, height: 300 },
large: { width: 600, height: 600 }
};
thumbnailQueue.process(async (job) => {
const { imageId, imagePath } = job.data;
try {
for (const [size, dimensions] of Object.entries(SIZES)) {
const thumbPath = imagePath.replace(
'/originals/',
`/thumbnails/${size}/`
);
await fs.mkdir(path.dirname(thumbPath), { recursive: true });
await sharp(imagePath)
.resize(dimensions.width, dimensions.height, {
fit: 'cover',
position: 'center'
})
.toFile(thumbPath);
await pool.query(
'INSERT INTO thumbnails (image_id, size, path) VALUES ($1, $2, $3)',
[imageId, size, thumbPath]
);
job.progress((Object.keys(SIZES).indexOf(size) + 1) / Object.keys(SIZES).length * 100);
}
return { success: true };
} catch (err) {
throw err;
}
});
export default thumbnailQueue;