Slonik Transaction Middleware for Express.js
raw JSON →express-slonik provides an Express.js middleware for managing PostgreSQL transactions using the Slonik client library. It simplifies the integration of database transactions into Express route handlers, ensuring that all database operations within a request either commit successfully or roll back entirely. The current stable version is 3.2.0, released in October 2023. The project maintains a frequent release cadence, primarily to align with new major versions of its peer dependency, Slonik, adding support for recent Slonik versions shortly after their release. Its key differentiator is its zero-dependency approach (beyond Express and Slonik itself) and its focus on robust transaction management within the Express middleware pattern, including support for isolation levels and explicit transaction control across multiple handlers.
Common errors
error TypeError: Cannot read properties of undefined (reading 'one') ↓
transaction.begin() is used before the route handler that accesses req.transaction. Verify slonik is installed and meets the express-slonik peer dependency requirements (e.g., npm ls slonik or yarn why slonik). error Error: Peer dependency "slonik" is not met. Expected ">=33.0.0 <38.0.0" but found "^32.0.0". ↓
slonik dependency to a compatible version, e.g., npm install slonik@^37 or yarn add slonik@^37. error SyntaxError: Cannot use import statement outside a module ↓
require() syntax (const createMiddleware = require('express-slonik').default;) or configure your project to use ES Modules by adding "type": "module" to your package.json and using .mjs file extensions if necessary. Warnings
breaking Version 3.0.0 introduced a breaking change by upgrading to Slonik v33. This change is not backward compatible with older Slonik versions due to API differences in Slonik itself. ↓
breaking Version 2.0.0 dropped support for Slonik versions 29 and below, requiring Slonik v30+. ↓
gotcha Transaction middleware relies on `req.transaction` being available. If other middleware modifies `req` in a conflicting way, it might cause issues. ↓
gotcha Failing to explicitly call `transaction.end()` or having unhandled errors within a transactional route handler will automatically commit/rollback the transaction. While this is often desired, it can lead to unexpected behavior if manual control is intended. ↓
Install
npm install express-slonik yarn add express-slonik pnpm add express-slonik Imports
- createMiddleware wrong
import { createMiddleware } from 'express-slonik';correctimport createMiddleware from 'express-slonik'; - TransactionContext
import { type TransactionContext } from 'express-slonik'; - createMiddleware (CJS) wrong
const createMiddleware = require('express-slonik');correctconst createMiddleware = require('express-slonik').default;
Quickstart
import express from 'express';
import { createPool, sql, NotFoundError } from 'slonik';
import createMiddleware from 'express-slonik';
import { z } from 'zod';
import { Server } from 'http';
const userSchema = z.object({
id: z.number().int(),
name: z.string(),
email: z.string().email(),
});
export const createExpressApp = ({ app, pool }) => {
const transaction = createMiddleware(pool);
app.get(
'/user/:id',
// Starts the transaction.
transaction.begin(),
async (req, res, next) => {
try {
const userId = parseInt(req.params.id, 10);
if (isNaN(userId)) {
return res.status(400).json({ message: 'Invalid user ID' });
}
const user = await req.transaction.one(
sql.type(userSchema)`SELECT * FROM users WHERE users.id = ${userId}`
);
res.json(user);
} catch (error) {
if (error instanceof NotFoundError) {
res.status(404).json({
name: error.name,
message: `User with given id (${req.params.id}) not found.`,
});
return;
}
next(error);
}
},
// Optional: Explicitly commits the transaction. If omitted, it auto-commits on response send or rolls back on error.
transaction.end()
);
return app;
};
/**
* Gracefully attempt to shut down the server.
*/
async function shutdownHandler(server: Server, pool) {
return async function () {
server.close();
await pool.end(); // Ensure the Slonik pool is also terminated
};
}
(async function () {
const app = express();
// Ensure DATABASE_URL is set in your environment variables, e.g., 'postgres://user:pass@host:port/database'
const pool = await createPool(process.env.DATABASE_URL ?? 'postgres://user:password@localhost:5432/mydb');
const server = createExpressApp({ app, pool }).listen(8080, () => {
console.log('Server listening on http://localhost:8080');
});
process
.on('SIGTERM', shutdownHandler(server, pool))
.on('SIGINT', shutdownHandler(server, pool));
})();