From 2504766be75ad14268514457a9fcc525ce4fedb9 Mon Sep 17 00:00:00 2001 From: Steve Dogiakos Date: Thu, 11 Jun 2026 22:03:56 -0600 Subject: [PATCH] perf(db): reuse prepared statements on hot paths - Prepare the user_accounts role lookup once in the auth middleware; it runs on nearly every authenticated request - Prepare session store get/set/destroy/purge statements once in the constructor instead of per request - Prepare the per-check SELECT once per PDF job instead of once per check --- src/lib/SessionStore.js | 16 +++++++++------- src/middleware/auth.js | 14 +++++++------- src/routes/pdf.js | 3 ++- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/lib/SessionStore.js b/src/lib/SessionStore.js index bf9d8ef..87d89d9 100644 --- a/src/lib/SessionStore.js +++ b/src/lib/SessionStore.js @@ -7,16 +7,20 @@ const { Store } = require('express-session'); class SessionStore extends Store { constructor(db) { super(); - this.db = db; + // Prepared once — get/set run on every request + this.getStmt = db.prepare('SELECT sess, expired FROM sessions WHERE sid = ?'); + this.setStmt = db.prepare('INSERT OR REPLACE INTO sessions (sid, sess, expired) VALUES (?, ?, ?)'); + this.delStmt = db.prepare('DELETE FROM sessions WHERE sid = ?'); + this.purgeStmt = db.prepare('DELETE FROM sessions WHERE expired < ?'); // Purge expired sessions every 10 minutes setInterval(() => { - try { db.prepare('DELETE FROM sessions WHERE expired < ?').run(Date.now()); } catch (_) {} + try { this.purgeStmt.run(Date.now()); } catch (_) {} }, 10 * 60 * 1000).unref(); } get(sid, cb) { try { - const row = this.db.prepare('SELECT sess, expired FROM sessions WHERE sid = ?').get(sid); + const row = this.getStmt.get(sid); if (!row) return cb(null, null); if (Date.now() > row.expired) { this.destroy(sid, () => {}); @@ -33,16 +37,14 @@ class SessionStore extends Store { ? sess.cookie.maxAge : 7 * 24 * 60 * 60 * 1000; const expired = Date.now() + maxAge; - this.db.prepare( - 'INSERT OR REPLACE INTO sessions (sid, sess, expired) VALUES (?, ?, ?)' - ).run(sid, JSON.stringify(sess), expired); + this.setStmt.run(sid, JSON.stringify(sess), expired); cb(null); } catch (e) { cb(e); } } destroy(sid, cb) { try { - this.db.prepare('DELETE FROM sessions WHERE sid = ?').run(sid); + this.delStmt.run(sid); cb(null); } catch (e) { cb(e); } } diff --git a/src/middleware/auth.js b/src/middleware/auth.js index 545a16d..60633e1 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -2,6 +2,11 @@ const db = require('../db/database'); +// Prepared once — this lookup runs on nearly every authenticated request +const accountRoleStmt = db.prepare( + 'SELECT role FROM user_accounts WHERE user_id = ? AND account_id = ?' +); + function requireAuth(req, res, next) { if (!req.session || !req.session.userId) { return res.status(401).json({ error: 'Not authenticated.' }); @@ -28,10 +33,7 @@ function requireEditor(req, res, next) { function canAccessAccount(session, accountId) { if (!session || !session.userId) return false; if (session.role === 'admin') return true; - const row = db.prepare( - 'SELECT 1 FROM user_accounts WHERE user_id = ? AND account_id = ?' - ).get(session.userId, accountId); - return !!row; + return !!accountRoleStmt.get(session.userId, accountId); } // Returns true if the user has editor (write) access to the given account. @@ -39,9 +41,7 @@ function canAccessAccount(session, accountId) { function isEditorForAccount(session, accountId) { if (!session || !session.userId) return false; if (session.role === 'admin') return true; - const row = db.prepare( - "SELECT role FROM user_accounts WHERE user_id = ? AND account_id = ?" - ).get(session.userId, accountId); + const row = accountRoleStmt.get(session.userId, accountId); return !!(row && row.role === 'editor'); } diff --git a/src/routes/pdf.js b/src/routes/pdf.js index 3d090ba..e21cb59 100644 --- a/src/routes/pdf.js +++ b/src/routes/pdf.js @@ -30,10 +30,11 @@ router.post('/', async (req, res) => { } // Fetch checks in the order provided; verify each belongs to the declared account + const checkStmt = db.prepare('SELECT * FROM checks WHERE id = ?'); let checks; try { checks = checkIds.map(id => { - const check = db.prepare('SELECT * FROM checks WHERE id = ?').get(id); + const check = checkStmt.get(id); if (!check) throw new Error(`Check ID ${id} not found`); if (check.account_id !== resolvedAccountId) throw new Error(`Check ID ${id} does not belong to this account`); return check;