javascript 41 lines · 7 steps

A per-request transaction middleware in Express

An Express middleware that opens a database transaction per request and commits or rolls it back based on the response outcome.

Explained by highlit
1const { pool } = require('./db');
2 
3function withTransaction() {
4 return async (req, res, next) => {
5 const client = await pool.connect();
6 req.db = client;
7 
8 let settled = false;
9 const finalize = async (commit) => {
10 if (settled) return;
11 settled = true;
12 try {
13 await client.query(commit ? 'COMMIT' : 'ROLLBACK');
14 } catch (err) {
15 console.error('transaction finalize failed', err);
16 } finally {
17 client.release();
18 }
19 };
20 
21 try {
22 await client.query('BEGIN');
23 } catch (err) {
24 client.release();
25 return next(err);
26 }
27 
28 res.on('finish', () => {
29 const success = res.statusCode < 400;
30 finalize(success);
31 });
32 
33 res.on('close', () => {
34 if (!res.writableEnded) finalize(false);
35 });
36 
37 next();
38 };
39}
40 
41module.exports = { withTransaction };
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Tying transaction commit/rollback to the response lifecycle keeps request handlers free of manual cleanup.
  2. 2A one-shot guard flag prevents double-finalizing when multiple response events fire.
  3. 3Always release pooled connections in a finally block so a failed query never leaks a client.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A per-request transaction middleware in Express — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code