ExpressJS can have a chain of middlewares for a single route, which at the very least allows to split input validation logic from the actual handling of the inputs. The usual pattern looks like this:
import { Router } from "express";
const router = Router();
router.use("/entity/:id", async (req, res, next) => {
try {
// validation logic
const isValid = ...
// short circuit if validation fails
if (!isValid) {
throw new Error("error message")
}
// get to the next middleware in chain
next()
} catch (error) {
// pass the error to the error-handling middleware chain instead
next(error)
}
});
router.get("/entity/:id", async (req, res, next) => {
try {
// route handling logic
...
} catch (error) {
next(error)
}
});
This works fine as long as validation middleware doesn't require database calls. But in the most basic usecase, auth, it does.
So auth code looks like this:
import { Router } from "express";
// `db` object created as per instructions
// https://vitaly-t.github.io/pg-promise/index.html
import { db } from "#db"
const router = Router();
router.use("/auth", async (req, res, next) => {
try {
// key retrieval logic
// assume it throws on any mistake
const authKey = ...;
// retrieve session by key
const session = await db.one(
"...",
{ authKey }
)
// throw if it's not found for whatever reason
if (!session) {
throw new Error("error message")
}
// save the session value for the request duration
// so the middleware/handlers can access
res.locals.session = session
// get to the next middleware in chain
next()
} catch (error) {
// pass the error to the error-handling middleware chain instead
next(error)
}
});
router.get("/auth/:id/posts", async (req, res, next) => {
try {
const { id } = req.params;
// it is guarantied to be always there because
// of the middleware before
const session = res.locals.session;
const posts = await db.manyOrNone("...", {session, id})
return res.status(200).json(posts);
} catch (error) {
next(error)
}
});
The issue here is these 2 middlewares will use 2 separate connections, and it will only get worse with more complicated routing.
Is there a way to create a transaction/task object, which is an argument for the callback for db.tx()
/db.task()
, which could be passed around outside of these methods?