Best practice to pass db and logger to routes #4733
-
to pass single instance references like a logger and/or db to routes, I see three options currently. First is to attach it to the request object via a middleware: app.use(function(req,res,next){ or in app as: then to use: Next way is to call some init method on the controller, ie: Last is a variation on the above which forces init to happen once when the module is required: and in your user.js, set your export like so: This is what I really hate about vanilla JS, and even a framework like Express. It's part of the reason why frameworks like react are so popular because there are set ways to do things. Ok, back to the topic. What's the best practice for passing single instance things like a logger and database to all your routes/controllers? Unfortunately If you search for "express best practices for routers", you'll get dozens of variations. Is there some goto reference/bible? Thanks for any advise/feedback, |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
To really be able to answer your question, it would be import to state what the expected lifecycle of those objects are. Different lifecycles would have different constraints to where you can place them for access in your routes. As for best practices, I think the Node.js ecosystem still has not determined what these are, and as a web framework, this is where express itself is "unopionated" on: how you want to perform IoC/DI in your application. |
Beta Was this translation helpful? Give feedback.
-
I'll add some opinionated design patterns to the topic for anyone who might need it. Since getting the db client is normally a single line of code, and in our codebase we tend to use a db client on authenticated endpoints, we prefer to create a middleware that not only obtains the db client but also manages user authentication: const { Pool } = require('pg')
const pool = new Pool()
// Create an authentication middleware
const auth = async (req, res, next) => {
// Get the db client from pool
const client = await pool.connect()
let user
if (req.body.email && req.body.password) {
// Manage authentication with email - password
const result = await client.query(
'select id from users where email = $1 and password = crypt($2, password)',
[req.body.email, req.body.password],
)
user = result.rows[0]
} else if (req.cookies) {
// Manage authentication with cookies
// ...
}
if (!user)
throw new Error('User is not authenticated')
// Save db client and user on request object,
// but it could be more apropiately to save it on res.locals:
// http://expressjs.com/en/api.html#res.locals
req.db = client
req.user = user
next()
})
// Get user profile, using auth middleware
app.get('/profile', auth, async (req, res) => {
// We are using the db client stored in req.db
// and the user stored in req.user from auth middleware
const result = await req.db.query(
'select firstname, lastname from profiles where id = $1',
[req.user.id],
)
// We will not be doing more queries, so we can release the client back to the pool
req.db.release()
res.send(result.rows[0])
})
// Don't forget to handle the client release in case of errors
app.use((err, req, res, next) => {
// Release client to avoid a leak
if (req.db && !req.db._events.error) {
req.db.release()
}
// Copied from express documentation
if (res.headersSent)
return next(err)
res.status(500)
res.render('error', { error: err })
} In case I need to make a request to the database without authentication I can get the db client directly on the endpoint, there is no need to create a middleware, since I just need one line of code to get the db client: const { Pool } = require('pg')
const pool = new Pool()
// Get users count
app.get('/users/count', async (req, res) => {
// Get the db client from pool
const client = await pool.connect()
const result = await client.query('select count(*) from profiles')
// We will not be doing more queries, so we can release the client back to the pool
client.release()
res.send(result.rows[0])
})
// Don't forget to handle the client release in case of errors
app.use((err, req, res, next) => {
// Release client to avoid a leak
if (req.db && !req.db._events.error) {
req.db.release()
}
// Copied from express documentation
if (res.headersSent)
return next(err)
res.status(500)
res.render('error', { error: err })
} |
Beta Was this translation helpful? Give feedback.
To really be able to answer your question, it would be import to state what the expected lifecycle of those objects are. Different lifecycles would have different constraints to where you can place them for access in your routes.
As for best practices, I think the Node.js ecosystem still has not determined what these are, and as a web framework, this is where express itself is "unopionated" on: how you want to perform IoC/DI in your application.