How to Process File Uploads in the Background
File processing is one of the most common causes of slow API responses. Image resizing, video transcoding, PDF generation — these operations take seconds to minutes. Users shouldn’t wait. Offload them as background jobs.
The Problem
// Slow: user waits for processing to complete
app.post('/upload', async (req, res) => {
const file = await saveToS3(req.file);
const processed = await processImage(file.url, { width: 800 }); // 5-30 seconds
const thumbnail = await generateThumbnail(file.url); // 2-10 seconds
await saveToDatabase({ original: file.url, processed, thumbnail });
res.json({ success: true }); // User waited 7-40 seconds
});
The Solution
Step 1: Accept the file upload and store it
Keep your upload endpoint fast — store the file and respond:
app.post('/upload', async (req, res) => {
const file = await saveToS3(req.file);
await saveToDatabase({
id: file.id,
originalUrl: file.url,
status: 'processing',
});
// Offload processing to AsyncQueue (Step 2)
await aq.tasks.create({
callbackUrl: 'https://your-app.com/api/process-file',
payload: { fileId: file.id, fileUrl: file.url },
webhookUrl: 'https://your-app.com/api/on-file-processed',
retries: 3,
});
res.json({ fileId: file.id, status: 'processing' });
});
The endpoint now responds in milliseconds.
Step 2: Create an AsyncQueue task for processing
The task was created in Step 1 above. AsyncQueue calls your processing endpoint with the file URL payload and waits for completion — regardless of duration.
Step 3: Build the processing endpoint
app.post('/api/process-file', async (req, res) => {
const { fileId, fileUrl } = req.body;
const processed = await processImage(fileUrl, { width: 800 });
const thumbnail = await generateThumbnail(fileUrl);
res.json({
fileId,
processedUrl: processed.url,
thumbnailUrl: thumbnail.url,
});
});
Step 4: Receive the result via webhook
app.post('/api/on-file-processed', async (req, res) => {
const { fileId, processedUrl, thumbnailUrl } = req.body.result;
await updateDatabase(fileId, {
processedUrl,
thumbnailUrl,
status: 'completed',
});
await notifyUser(fileId, 'Your file is ready!');
res.status(200).json({ received: true });
});
Step 5: Update the user interface
On the frontend, use polling or WebSockets to notify the user when processing finishes:
// Simple polling
const checkStatus = async (fileId) => {
const res = await fetch(`/api/files/${fileId}`);
const data = await res.json();
if (data.status === 'completed') {
showProcessedFile(data.processedUrl);
} else {
setTimeout(() => checkStatus(fileId), 2000);
}
};
Use Cases
- Image uploads: Resize, crop, apply filters, generate thumbnails
- Video uploads: Transcode to multiple formats and resolutions
- Document uploads: Convert DOCX to PDF, extract text, generate previews
- CSV/data imports: Parse large files and insert records into your database in bulk