else{
passport.authenticate("local")(req, res, function () {
res.redirect("/secrets");
});
}
Why is there no "." after authenticate("local") (here)and before (req, res, function () .........)
passport.authenticate returns a function. That returned function can then be called (like any function) - req, res, and another callback function are arguments that can be passed.
If you're finding it difficult to read, it might make more sense to break the returned function out into its own identifier.
const passportHandler = passport.authenticate("local");
passportHandler(req, res, function () {
res.redirect("/secrets");
});
Often, an even better approach would be to for the Passport middleware to be declared for the route itself, instead of declaring both a route callback and then passing down the req and res again. That is
app.post(
'someEndpoint',
passport.authenticate("local"),
(req, res) => {
res.redirect("/secrets");
}
)
if you can figure out a way around the else. passport.authenticate returns a route handler callback, and the (req, res) => is also a route handler callback..
Related
I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:
router.get(
"/",
asyncMiddleware(
routeProviderMiddleware(
async ({ y }) => ({
body: await db.query({x: y})
})
)
)
)
// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}
// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
}
What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these.
Even combining the two middlewares together would be nice, but I didn't manage.
I use following approach:
Create asyncWrap as helper middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
module.exports = asyncWrap
All your routes/middlewares/controllers should use this asyncWrap to handle errors:
router.get('/', asyncWrap(async (req, res, next) => {
let result = await db.query({x: y})
res.send(result)
}));
At app.js, the last middleware will receive the errors of all asyncWrap:
// 500 Internal Errors
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.send({
message: err.message,
errors: err.errors,
})
})
Express 5 automatically handles async errors correctly
https://expressjs.com/en/guide/error-handling.html currently says it clearly:
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
I have shown that in an experiment at: Passing in Async functions to Node.js Express.js router
This means that you will be able to just make the callback async and use await from it directly without any extra wrappers:
router.get("/", async (req, res) =>
const obj = await db.query({x: req.params.id})
// Use obj normally.
)
and errors will be correctly handled automatically.
Express permits a list of middlewares for a route and this approach sometimes works for me better than higher-order functions (they sometimes look like an overengineering).
Example:
app.get('/',
validate,
process,
serveJson)
function validate(req, res, next) {
const query = req.query;
if (isEmpty(query)) {
return res.status(400).end();
}
res.locals.y = query;
next();
}
function process(req, res, next) {
Promise.resolve()
.then(async () => {
res.locals.data = await db.query({x: res.locals.y});
next();
})
.catch((err) =>
res.status(503).end()
);
}
function serveJson(req, res, next) {
res.status(200).json(res.locals.data);
}
What you can do is add an error handlers after your routes. https://expressjs.com/en/guide/error-handling.html
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
What I ended up doing is unifying the wrappers like this:
const routeProvider = routeHandlerFn => async (req, res, next) => {
try {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
} catch(error) {
next(error)
}
}
This wrapper is all any route would need. It catches unexpected errors and provides the route handler with the needed params.
I have an Express application and I'm trying to put all my middleware in its own file. Some of the middleware functions need the db object and some don't.
It's pretty straightforward for the functions that don't need the db object, but given my code structure below, how can I reference the db object in doesNotNeedDbParam since it already has params req, res, and next?
somefile.js:
const router = express.Router()
const doesNotNeedDbParam = require('./middleware')().doesNotNeedDbParam
function foo () {
// Currently I have to call require and pass in the db object here b/c
// it's not set when requiring the function doesNotNeedDbParam
router.use(require('./middleware')(db).needsDbParam // <-- Is there a better way to do this so that I can require the file above and pass the db object in when it's set?
}
// Setup db object here
foo()
middleware.js
function doesNotNeedDbParam (req, res, next) {
...
}
function needsDbParam (req, res, next) {
// Where do I reference the db variable?
}
module.exports = (db) => {
return {
doesNotNeedDbParam: doesNotNeedDbParam,
needsDbParam: needsDbParam
}
}
Functional Approach
I think a good structure for this is to try currying your middleware. This is a pattern practiced by middleware such as body-parser and internally by Express itself with serve-static. This way, you only have to require once, and pass db where you need to, and don't where you don't need it:
// Instead of declaring each function directly as a middleware function,
// we declare them as a function that returns a middleware function
function doesNotNeedDbParam () {
return function (req, res, next) {
…
}
}
function needsDbParam (db) {
return function (req, res, next) {
// You can use db here along with req, res, next
}
}
// No need to export a function now
module.exports = {
doesNotNeedDbParam,
needDbParam,
};
Then, just require:
const middleware = require('./middleware');
…
router.use(middleware.doesNotNeedDbParam()); // Since this doesn't need anything, no argument
router.use(middleware.needsDbParam(db)); // You can pass db here now
If you're comfortable with ES6+ syntax, you can condense to:
const doesNotNeedDbParam = () => (req, res, next) => {
…
}
const needsDbParam = (db) => (req, res, next) => {
// Use db here
}
// Export object here...
Then:
const { doesNotNeedDbParam, needsDbParam } = require('./middleware');
…
router.use(doesNotNeedDbParam());
router.use(needsDbParam(db));
Attach Approach
There's also another way you can do this, by attaching a property to the req object once. This removes the need to repass db every single time you want it. Many other packages use this strategy. It goes something like this:
function attachDb (db) { // Still use curry approach here since we want db
return function (req, res, next) {
// Attaches the specified db to req directly
req.db = db;
}
}
function needsDbParam (req, res, next) { // No need for currying
// Now use req.db here
}
// Export your other middleware…
Then, use it like so, make sure attachDb is first so that the property is assigned before you use it:
router.use(attachDb(db)); // Before all other middleware that depend on req.db
…
// No need to call because this is already the middleware function,
// able to use req.db, which was assigned via attachDb
router.use(needDbParam);
Why not just declare module.exports as a single function:
module.exports = (db) => {
let module = {};
module.doesNotNeedDbParam = (req, res) => {
// Do Stuff
};
module.needsDbParam = (req, res) => {
// db now in scope
};
return module;
};
This is what your somefile.js would become:
const router = express.Router();
const db = initializeDb();
const doesNotNeedDbParam = require('./middleware')().doesNotNeedDbParam;
router.use(require('./middleware')(db).needsDbParam);
You could also set it up once like this:
const middleware = require('./middleware')(db);
const doesNotNeedParam = middleware.doesNotNeedParam;
router.use(middleware.needsDbParam);
This isn't really any different than what you were doing before, but now you have access to db inside of needsDbParam. If your initializeDb function is async, then you will need to use Promise or some other async library to include after the db is set up.
Suppose I have a route like this:
app.get('/broken', (req, res) => {
throw new Error('Broken!');
});
This will never send a response to clients.
However, I can add a middleware for all errors:
const errorMiddleware = (error, req, res, next) => {
if (error) {
console.error(error);
return res.status(500)
.json({
message: 'Internal server error',
});
}
next(error);
};
But this will not work for async routes, because they do not throw directly.
For example, this will not work:
app.get('/broken', async (req, res) => {
throw new Error('Broken!');
});
So I can create a wrapper like this:
const asyncRoute = f => (req, res, next) => {
return Promise.resolve(f(req, res, next)).catch(next);
};
app.get('/broken', asyncRoute(async (req, res) => {
throw new Error('Broken!');
}));
But this is a real pain, because now I have to call this function for every route!
What is a better way of handling this?
The answer to Is there a way to wrap an await/async try/catch block to every function? is just what I describe above
The answer to how to use Promise with express in node.js? does not use await
Fundamentally, you don't want to directly pass an async function into Express's app.get, because app.get doesn't handle the promise the function returns. So you'll need to wrap those async handlers (as you're doing).
You can avoid having to do it every time by giving yourself a utility method at the top of the module:
const appGet = handler => app.get(asyncRoute(handler));
then use it instead of app.get:
appGet('/broken', async (req, res) => {
throw new Error('Broken!');
});
At some point (probably not right now), you might want to look at Koa.
Lets imagine we have a function as so:
Router.get('/', async function (req, res, next) {
let result = await someFunction().catch(next)
someOtherCall()
})
If this errors out, it continues on to the global error handler by calling next(error_reason). However, if the someFunction() fails, we don't want someOtherCall() to run at all. At the moment, I can see two ways of fixing this:
// Suggestion from https://stackoverflow.com/q/28835780/3832377
Router.get('/', async function (req, res, next) {
let result = await someFunction().catch(next)
if (!result) return // Ugly, especially if we have to run it after every call.
someOtherCall()
})
Router.get('/', async function (req, res, next) {
let result = someFunction().then(() => {
// Much less work, but gets us back to using callbacks, which async/await were
// meant to help fix for us.
someOtherCall()
}).catch(next)
})
Is there a simpler way to stop a function from executing if any of the functions call that doesn't mean adding another statement after every function call or using callbacks?
You can simply use try-catch:
Router.get('/', async function (req, res, next) {
try {
let result = await someFunction()
someOtherCall()
}
catch(exception) {
next(exception)
}
})
what is the difference between:
function setLocale(req, res, next) {
req.params.locale = req.params.locale || 'pt';
res.cookie('locale', req.params.locale);
req.i18n.setLocale(req.params.locale);
console.log(req.params.locale);
next();
}
app.get('/:locale?', setLocale, function(req, res) {
res.render("index");
});
And this:
app.use(setLocale);
function setLocale(req, res, next) {
req.params.locale = req.params.locale || 'pt';
res.cookie('locale', req.params.locale);
req.i18n.setLocale(req.params.locale);
console.log(req.params.locale);
next();
}
app.get('/:locale?', function(req, res) {
res.render("index");
});
??
Only the first is working, if i try to use app.use, the code will broke cause req.params.locale will be undefined.
The problem is that when you use app.use(setLocale); all you calls will be passed that function. Even if you call the url / that code will run and then param will be undefined.
The fisrt option you have (app.get('/:locale?', setLocale,) you use that function only when that url matches and there is a locale that you can use inside the function.
app.use will add the middleware to the stack and use it before each request is processed, always, regardless of route, method etc.
In the first example the middleware is added as a callback function to that route only, as app.get accepts multiple callbacks, and calling next moves to the next callback etc
app.get('/', function(req, res, next) {
next(); // move to next
}, function(req, res, next) {
// continues here when next() is called in previous callback etc.
});
this means that in the first example the setLocale function is only called when the route matches /:locale?, while in the second example using app.use will always call the setLocale function before the routes callback is executed.
Unfortunately req.params is not available in app.use as it depends on the router and is added later, so you're probably stuck with including the function as a callback to every route, and you could probably do that with app.all('*')
function setLocale(req, res, next) {
req.params.locale = req.params.locale || 'pt';
res.cookie('locale', req.params.locale);
req.i18n.setLocale(req.params.locale);
next();
}
app.all('*', setLocale);
app.get('/:locale?', function(req, res) {
res.render("index");
});