How to propagate AbortController signals through Next.js API routes to prevent orphaned database operations on client disconnect
0 reputation · 22 Mar 2023, 21:52 UTC
0 reputation · 22 Mar 2023, 21:52 UTC
Next.js API routes and serverless functions do not automatically propagate AbortController signals from incoming requests to downstream operations like database queries or external API calls. When a client disconnects or a timeout occurs, Node.js may terminate the serverless function mid-execution, potentially leaving database transactions in an inconsistent state.
The hosting platform's execution timeout (Vercel: 10s hobby/60s pro, AWS Lambda: 15m max) can also force termination of long-running operations. While Node.js provides AbortController for request cancellation, Next.js does not automatically wire these signals through its fetch wrappers or database connection layers.
This creates a gap where developers must manually implement signal propagation to ensure graceful cleanup of resources and data integrity when requests are cancelled or time out.
Which patterns effectively propagate AbortSignal from Next.js API route handlers to database queries and external service calls, and how do they behave consistently across Vercel, AWS Lambda, and custom Node.js servers with varying timeout configurations?
35775 reputation · 23 Mar 2023, 03:54 UTC
Manually create an AbortController inside the route handler, bind its signal to the incoming request’s close lifecycle, and pass that signal to every cancellable database query or external HTTP call. Next.js does not automatically propagate request abort signals to database drivers or upstream fetch clients.
req object; client disconnect usually emits close on req.socket.fetch(url, { signal }) and axios honor AbortSignal and reject when the controller is aborted.signal.aborted or driver-native cancellation before commit.Next.js API routes are thin wrappers around Node.js HTTP or Web Request APIs. The framework does not inject an abort signal into Prisma, pg, mysql2, or upstream fetch calls by default. When a client disconnects, the socket closes, but in-flight database transactions keep running unless your code explicitly rolls them back or releases the connection.
const controller = new AbortController();
const { signal } = controller;
req.socket?.on('close', () => controller.abort());
fetch('https://api.example.com/data', { signal });
signal.aborted before starting a transaction and after awaiting each query. If aborted, roll back, release the client, and throw:if (signal.aborted) {
await client.query('ROLLBACK');
client.release();
throw new DOMException('Aborted', 'AbortError');
}
try {
await doWork({ signal });
} catch (err) {
if (err.name === 'AbortError') {
await cleanupDatabase();
return new Response(null, { status: 499 });
}
throw err;
}
| Platform | Timeout | Behavior on disconnect |
|---|---|---|
| Vercel Hobby | 10s | Function may be killed before abort cleanup completes. |
| Vercel Pro | 60s | More time for rollback, but still bounded. |
| AWS Lambda | 15m maximum | Abort signal fires, but forced termination can leave orphaned work. |
| Custom Node.js | Server-configured | Depends on keep-alive and server timeout settings. |
curl --max-time 2 http://localhost:3000/api/route, then close the tab.Missing diagnostic detail: Are you using Pages API routes or App Router Route Handlers, and which runtime? The signal source changes between req.socket and Web Request properties, and Edge runtime limits socket access.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 23 Mar 2023, 01:26 UTC
Effective cancellation hinges less on Next.js and more on the underlying database client library. Simply receiving the AbortSignal in the API route handler is insufficient if the DB driver does not expose a mechanism to listen to or respect that signal during query execution.