0

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?

Biller Builder
  • 303
  • 3
  • 10
  • Technically, you can use manual connection - method [connect](http://vitaly-t.github.io/pg-promise/Database.html#connect), but for an API service, a manually managed connection is not a good idea ;) – vitaly-t Jan 03 '23 at 15:41
  • `The issue here is these 2 middlewares will use 2 separate connections, and it will only get worse with more complicated routing`. What is the issue you're referring to? There shouldn't be, it is normal use practice. There must be something else wrong in your project or db setup. What is your pool size? (is where you should start) – vitaly-t Jan 03 '23 at 16:06
  • `There shouldn't be, it is normal use practice.` I don't think using `n` connections per request, where `n` is the amount of middlewares in the chain with database calls is a normal use practice. At the very least the entire request cycle should be able to run as a single task. `connect` looks like it accomplishes this. But then how do you run an entire request as a single transaction? Sorta like python's [`connection.commit()`/`connection.rollback()`](https://peps.python.org/pep-0249/#commit). – Biller Builder Jan 04 '23 at 08:13
  • You are asking the wrong questions here, I'm afraid, and you are attempting to deviate from all usage patterns documented in `pg-promise`. You cannot merge a complex transaction logic between multiple concurrent API entry points, because transactions are atomic. – vitaly-t Jan 04 '23 at 21:29
  • They aren't concurrent, several middlewares run sequentially for a single endpoint for a single request. No different from calling query methods on the `task` argument of `db.tx()` method. – Biller Builder Jan 06 '23 at 15:07
  • Your own code examples show all HTTP handlers as `async`. – vitaly-t Jan 06 '23 at 17:47
  • `async` doesn't imply concurrency. – Biller Builder Jan 14 '23 at 04:07

0 Answers0