patterntypescriptexpressModerate
Long-Running Operations: 202 Accepted Pattern
Viewed 0 times
202 Acceptedlong-running operationasync APIjob queuepollingLocation header
Problem
Operations that take more than a few seconds (report generation, data export, video encoding) cannot be handled synchronously in an HTTP request without client timeouts.
Solution
Return 202 Accepted immediately with a polling URL for the operation status.
// Initiate long-running job
app.post('/reports', async (req, res) => {
const jobId = await queue.enqueue('generate-report', req.body);
res.status(202).json({
jobId,
status: 'pending',
statusUrl: `${req.baseUrl}/jobs/${jobId}`,
estimatedDuration: 30, // seconds
});
res.setHeader('Location', `/jobs/${jobId}`);
});
// Poll for status
app.get('/jobs/:id', async (req, res) => {
const job = await queue.getJob(req.params.id);
if (!job) return res.status(404).json({ error: 'Job not found' });
if (job.status === 'complete') {
// Redirect to result or return inline
return res.redirect(303, `/reports/${job.resultId}`);
}
res.json({
jobId: job.id,
status: job.status, // 'pending' | 'running' | 'failed'
progress: job.progress,
statusUrl: `/jobs/${job.id}`,
});
});Why
202 Accepted means 'the request was accepted for processing, but processing is not yet complete'. The Location header tells clients where to poll for status.
Gotchas
- Include a Retry-After header to hint at the polling interval — prevents aggressive polling.
- On completion, redirect with 303 See Other to the result resource, not 301/302.
- Job status should be stored durably — in-memory storage loses jobs on server restart.
Revisions (0)
No revisions yet.