Express.js - Access target route details in Application-level Middleware - javascript

Background:
I have an express application with some simple routes and Router-level Middleware. I want to register an Application-Level Middleware.
The Problem
In Router-Level Middlewares. I can access req.route object. But I can not access the same object inside an Application-Level Middleware.
I can understand this, as inside Application-Level middlewares the program is not inside the route yet.
But is there any way to get req.route object or something equivalent to req.route.path inside global middlewares?
req.path or req.originalUrl contains the real url not the route path.
Example
const express = require('express');
const app = express();
const port = 3232;
app.use((req, res, next) => {
const route = req.route; // It is an application level middleware, route is null
return next();
});
app.get('/test/:someParams', (req, res) => {
const route = req.route; // can access req.route here because it is a Router-level middleware
console.log(route.path)
console.log(req.path)
return res.send('test')
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Output
Request: GET#localhost:3232/test/33333
/test/33333 // I don't need this.
/test/:someParams // This is what I want to get inside the Application-Level Middleware
Alternative Solution
An alternative solution to this problem can be as the following
const express = require('express');
const app = express();
const port = 3232;
function globalMiddleware(req, res, next) {
const route = req.route;
console.log(route) // can access it
return next();
}
app.use((req, res, next) => {
const route = req.route; // It is an application level middleware, route is null
return next();
});
app.get('/test/:someParams', globalMiddleware, (req, res) => {
const route = req.route; // can access req.route here because it is a Router-level middleware
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
But injecting the same middleware to each and all of my routes does not sound like a smart solution. Specially on bigger applications.
A dump of router object
{
"path":"/test/:someParams",
"stack":[
{
"name":"globalMiddleware",
"keys":[
],
"regexp":{
"fast_star":false,
"fast_slash":false
},
"method":"get"
},
{
"name":"<anonymous>",
"keys":[
],
"regexp":{
"fast_star":false,
"fast_slash":false
},
"method":"get"
}
],
"methods":{
"get":true
}
}
the path key is the thing I want to get. Please note that req.route.path is not the same as req.path

I was having this difficulty too. I didn't find anything on the internet that would solve it so I tried to search the express code itself for what it did to find the route. Basically it is the same thing as the code below, it looks for which regexp is valid for that route. (Google Translate)
const url = req.originalUrl.split('?')[0] // Routes with query
const layer = req.app._router.stack.find(layer => {
return layer.regexp.exec(url) && layer.route
})
So you can access the original path:
console.log(layer.route.path)

What data do you expect in req.route?
you can use req.url, req.method, req.originalUrl, etc...
or in Application level middleware you can add new field to req object
req.customRoute = {yourField: "yourValue"}
and this field will available in route level middleware

You can use middleware as per request
const middleware = (req, res, next) => {
// Append what you want in req variable
req.route = "abc" // let abc
req.path = "path" // req.route.path
next();
}
You can get it here from middleware
app.get('/test/:someParams', middleware, (req, res) => {
console.log(req.params.someParams)
console.log(req.route) // "abc"
console.log(req.path) // "path"
});
For application level middleware
app.use((req, res, next) => {
req.route = "abc" // let abc
req.path = "path" // or req.route.path
next()
})

Related

Passport-Local and Express Router Issue with Imports

I have an Express.js app with a Passport-local Auth, I'm tying to implement express router to my app since it's getting really hard to debug
This is an example of my main file (app.js)
const express = require('express')
const app = express()
const passport = require('passport')
const testing = require('./routes/file')
app.use(passport.initialize())
app.use(passport.session())
app.use('/router', testing)
const initializePassport = require('./passport-config');
initializePassport.initialize(passport);
app.get('/', checkAuthenticated, (req, res) => {
let user = data.key
res.send(`Hi ${user}`)
})
app.post('/login', checkNotAuthenticated, passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}))
function checkAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.redirect('/login')
}
function checkNotAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return res.redirect('/')
}
next()
}
app.listen(3000)
This is what my main file is like, this works perfectly fine
and just to make things clear, The function are to check if a user is authenticated or not, also my passport-config returns something called data.key which is basically the username of the user.
Now I've created a router here app.use('/router', testing) with the route as /router for testing purposes
Now this is my file.js that is being used by express
const express = require('express'), router = express.Router();
var bodyParser = require('body-parser');
// Handle POST requests
router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json());
router.get('/test', checkAuthenticated, (req, res) => {
let user = data.key;
res.send(`Hello ${user}, this is coming from the router`)
})
module.exports = router;
Now as expected when I open the site at localhost:3000/router/test it returns a undefined error for data.key since it's not imported and then when I comment that out the line causing the error then checkAuthenticated doesn't work, even when I'm not logged in I can get access to the page, now this is the problem, how do I fix it, I've gone through the entire express router docs I couldn't find luck there.
Thanks
Your function is not in the global scope you need to set it to global to be able to see it like:
GLOBAL.authFunction = function(){
}
Suggestion:
1- make a module for auth name it authModule.
2- import the module auth check middleware where ever you want.

Send variables to all routes in expressjs

I have req.session variables that I am setting upon login, like so:
req.session.loggedin = true
req.session.firstname = loginDetails.firstName;
what I want to do is pass this information to ALL routes (I have nearly 60, and don't want to go through all of them and add these in manually), and also every route I have calls the front-end using this:
res.render('page.ejs', {data: rows}), so I would ideally want it to pass it to the front-end pages so I can access them there too. Not sure if this is possible, but worth a shot! thx 4 the help in advance!
You can create a middleware function and add variables to an existing express-session.
app.js
const express = require("express");
const session = require("express-session");
// express app
const app = express();
app.use(express.json());
// init example session with express session
app.use(session({ resave: true, secret: "123456", saveUninitialized: true }));
// use a middleware function
app.use((req, res, next) => {
if (!req.session.initialised) {
// init variables you want to set in req.session
req.session.loggedin = true;
req.session.firstname = "john doe";
}
next();
});
// test api endpoint
app.use("/testroute", require("./routes/api/test"));
// run server
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Then you can query your variables like req.session.firstname in any other route in the request object. You can also update it from your routes
test.js
// testroute
const router = express.Router();
router.get("/", async (req, res) => {
// get variables from request object
try {
console.log(req.session.firstname);
console.log(req.session.loggedin);
// this would update the session variable if uncommented
// req.session.firstname = "dagobert"
// console.log(req.session.firstname);
res.status(200).json({ message: `Your firstname is ${req.session.firstname}` });
} catch (error) {
res.status(400).json({ message: error.message });
}
});
module.exports = router;

get express params in an intercepting middleware

I would like to intercept a req.params item in an interception layer:
let's say I have an express server application instance:
const app = express();
with an interception layer:
app.use((req, res, next) => {
console.log(req.params.myParam);
next();
})
and many endpoints like this one:
app.get('/anything/:myParam', (req, res) => res.send('hello'))
this logs 'undefined' which is quite natural because when the interception middleware within the "use" is executed, the param name has not been defined yet. But I really need to known in this interception layer the value of myParam parameter with these constraints:
I known how the parameter is supposed to be named in the intercepted endpoints (myParam)
I can't know how the url is structured in the intercepted endpoints (can be /anything/:myParam, or /:myParam/anything and so on...)
Does anyone know a solution for that case ?
The comment from #indrajaj26 about using app.param() leads me on a working solution.
Here is the way I'm proceeding:
/** express application */
const app = express();
// Interception layer
app.param('myParam', (req, res, next, myParam) => {
/* Store myParam into something reusable (like locals as suggested by #nullromo for instance).
* I'm using zonejs in my case: creating a forked zone with myParam stored in zone properties.
*/
next();
});
// Consuming intercepted data. Anywhere in the callstack of any endpoint using "myParam" as request parameter:
console.log(Zone.current.getZoneWith('myParam')?.get('myParam'));
Thanks everyone for your ideas!
A simple solution would be to pass the middleware function when defining the route.
Example:
function middleware(req, res, next) {
console.log(req.params.myParam);
next();
}
app.get('/path', middleware, function(req, res) {
...
});
Full example:
const express = require('express');
const app = express();
function middleware(req, res, next) {
console.log(req.params.myParam);
next();
}
app.get('/path', middleware, function(req, res) {
...
});
app.listen(3000, () => console.log('server is online'));
You can store the params on the response.locals object for use by later middleware.
const express = require('express');
const app = express();
app.get('/:name', (request, response, next) => {
// generate some kind of result
const result = `Hello, ${request.params.name ?? 'world'}!`;
// save the result and the params onto the request.locals object
response.locals = { params: request.params, result };
// call the next middleware to pass on the params and result
next();
});
app.use((request, response, next) => {
// here you have the params since they were parsed by the previous middleware
console.log(`Intercepted: ${JSON.stringify(response.locals.params)}`);
// now actually send the response that you wanted to send
response.status(200).send(response.locals.result);
});
app.listen(3000, () => {
console.log('listening');
});
This requires storing the request.params on the response.locals object in every handler, which is not the greatest. But it also means that you don't have to specify the interceptor middleware every time, if that's a problem for your case.

Express server using default route, also serve API

The way I have this app set up, every request gets serviced by the same route that will do some JS serverside rendering:
server.use("*", (req, res) => {
console.log(`from the server route: ${req.path}`)
const context = {};
const serverRenderedHTML = ReactDOMServer.renderToString(
React.createElement(
StaticRouter,
{ location: req.url, context },
React.createElement(AdminApp)
)
);
if (context.url) {
res.redirect(context.url);
}
res.write(template({ serverRenderedHTML }));
res.end();
});
But, I would also like to use my express server as an api for some resources, routes set up like this:
//server.js
server.use("/api/products", productRoutes);
// products.js
router.get( (req, res) => {
var productQuery = Product.find({})
productQuery.exec(function(err, products){
res.json(products)
})
})
However, I am unable to hit the API, as all of my requests are getting picked up by that server.use function.
Is there any way to make sure that the routes under /api/ namespace are properly picked up? Should I just make a different server for the API?
Apparently, I needed to include an argument for the route. I had to change:
router.get( (req, res) => ...
to
router.get("/", (req, res) => ...

Rest with Express.js nested router

Suppose I want to have REST endpoints which look roughly like this:
/user/
/user/user_id
/user/user_id/items/
/user/user_id/items/item_id
CRUD on each if makes sense. For example, /user POST creates a new user, GET fetches all users. /user/user_id GET fetches just that one user.
Items are user specific so I put them under user_id, which is a particular user.
Now to make Express routing modular I made a few router instances. There is a router for user, and a router for the item.
var userRouter = require('express').Router();
userRouter.route('/')
.get(function() {})
.post(function() {})
userRouter.route('/:user_id')
.get(function() {})
var itemRouter = require('express').Router();
itemRouter.route('/')
.get(function() {})
.post(function() {})
itemRouter.route('/:item_id')
.get(function() {})
app.use('/users', userRouter);
// Now how to add the next router?
// app.use('/users/', itemRouter);
URL to item is descendents of the URL hierarchy of the user. Now how do I get URL with /users whatever to userRouter but the more specific route of /user/*user_id*/items/ to the itemRouter? And also, I would like user_id to be accessible to itemRouter as well, if possible.
You can nest routers by attaching them as middleware on an other router, with or without params.
You must pass {mergeParams: true} to the child router if you want to access the params from the parent router.
mergeParams was introduced in Express 4.5.0 (Jul 5 2014)
In this example the itemRouter gets attached to the userRouter on the /:userId/items route
This will result in following possible routes:
GET /user -> hello user
GET /user/5 -> hello user 5
GET /user/5/items -> hello items from user 5
GET /user/5/items/6 -> hello item 6 from user 5
var express = require('express');
var app = express();
var userRouter = express.Router();
// you need to set mergeParams: true on the router,
// if you want to access params from the parent router
var itemRouter = express.Router({mergeParams: true});
// you can nest routers by attaching them as middleware:
userRouter.use('/:userId/items', itemRouter);
userRouter.route('/')
.get(function (req, res) {
res.status(200)
.send('hello users');
});
userRouter.route('/:userId')
.get(function (req, res) {
res.status(200)
.send('hello user ' + req.params.userId);
});
itemRouter.route('/')
.get(function (req, res) {
res.status(200)
.send('hello items from user ' + req.params.userId);
});
itemRouter.route('/:itemId')
.get(function (req, res) {
res.status(200)
.send('hello item ' + req.params.itemId + ' from user ' + req.params.userId);
});
app.use('/user', userRouter);
app.listen(3003);
manageable nested routes...
I wanted a specific example of doing nested routes in a very manageable way in express 4 and this was the top search result for "nested routes in express". Here's an API that would have many routes that would need to be broken up for example.
./index.js:
var app = require('express')();
// anything beginning with "/api" will go into this
app.use('/api', require('./routes/api'));
app.listen(3000);
./routes/api/index.js:
var router = require('express').Router();
// split up route handling
router.use('/products', require('./products'));
router.use('/categories', require('./categories'));
// etc.
module.exports = router;
./routes/api/products.js:
var router = require('express').Router();
// api/products
router.get('/', function(req, res) {
res.json({ products: [] });
});
// api/products/:id
router.get('/:id', function(req, res) {
res.json({ id: req.params.id });
});
module.exports = router;
Nesting example in folder structure
I noticed some comments on "nesting folder structure". It is implied in this however not obvious so I added the section below. Here's a specific example of a nested folder structure for routes.
index.js
/api
index.js
/admin
index.js
/users
index.js
list.js
/permissions
index.js
list.js
This is more a general example of how node works. If you use "index.js" in folders similarly to how "index.html" works in web pages for a directory default, this will be easy to scale your organization based off of recursion without changing your entry points to code. "index.js" is the default document accessed when using require in a directory.
contents of index.js
const express = require('express');
const router = express.Router();
router.use('/api', require('./api'));
module.exports = router;
contents of /api/index.js
const express = require('express');
const router = express.Router();
router.use('/admin', require('./admin'));
module.exports = router;
contents of /api/admin/index.js
const express = require('express');
const router = express.Router();
router.use('/users', require('./users'));
router.use('/permissions', require('./permissions'));
module.exports = router;
contents of /api/admin/users/index.js
const express = require('express');
const router = express.Router();
router.get('/', require('./list'));
module.exports = router;
There is some DRY issues here possibly but it does lend itself well to encapsulation of concerns.
FYI, recently I got into actionhero and have found it to be full featured w/sockets and tasks, more like a true framework all-in-one flipping the REST paradigm on its head. You should probably check it out over going naked w/ express.
var userRouter = require('express').Router();
var itemRouter = require('express').Router({ mergeParams: true });
userRouter.route('/')
.get(function(req, res) {})
.post(function(req, res) {})
userRouter.route('/:user_id')
.get(function() {})
itemRouter.route('/')
.get(function(req, res) {})
.post(function(req, res) {})
itemRouter.route('/:item_id')
.get(function(req, res) {
return res.send(req.params);
});
app.use('/user/', userRouter);
app.use('/user/:user_id/item', itemRouter);
The key to the second part of your question is the use of the mergeParams option
var itemRouter = require('express').Router({ mergeParams: true });
From /user/jordan/item/cat I get a reponse:
{"user_id":"jordan","item_id":"cat"}
Using #Jason Sebring solution, and adapting for Typescript.
server.ts
import Routes from './api/routes';
app.use('/api/', Routes);
/api/routes/index.ts
import { Router } from 'express';
import HomeRoutes from './home';
const router = Router();
router.use('/', HomeRoutes);
// add other routes...
export default router;
/api/routes/home.ts
import { Request, Response, Router } from 'express';
const router = Router();
router.get('/', (req: Request, res: Response) => {
res.json({
message: 'Welcome to API',
});
});
export default router;
In the spirit of Express modular routers, we should have a separate router for users and for items. That router isn't part of our top-level application logic. We can nest it in our users' router instead.
Users router
const users = require('express').Router();
const items = require('./items');
//...
// Our root route to /users
albums.get('/', function(req, res, next) {
// res.send() our response here
});
// A route to handle requests to any individual user, identified by an user id
users.get('/:userId', function(req, res, next) {
let userId = req.params.userId;
// retrieve user from database using userId
// res.send() response with user data
});
// Note, this route represents /users/:userId/items because our top-level router is already forwarding /users to our Users router!
users.use('/:userId/items', items);
//...
module.exports = users;
Items router
// We need to merge params to make userId available in our Items router
const items = require('express').Router({ mergeParams: true });
//...
// The root router for requests to our items path
items.get('/', function(req, res, next) {
let userId = req.params.userId; // Here is where mergeParams makes its magic
// retrieve user's track data and render items list page
});
// The route for handling a request to a specific item
items.get('/:itemId', function(req, res, next) {
let userId = req.params.userId; // <-- mergeParams magic
let itemId = req.params.itemId;
// retrieve individual item data and render on single item page
});
//...
module.exports = items;
Source
try to add { mergeParams: true } look to simple example which it middleware use it in controller file getUser at the same for postUser
const userRouter = require("express").Router({ mergeParams: true });
export default ()=>{
userRouter
.route("/")
.get(getUser)
.post(postUser);
userRouter.route("/:user_id").get(function () {});
}
Express router(express.Router()) keeps params seprate so you would explicitly have to tell express to merge these params.
eg:
express.Router({ mergeParams: true })
//above line is answer to your question.
You need only one router, and use it like this:
router.get('/users');
router.get('/users/:user_id');
router.get('/users/:user_id/items');
router.get('/users/:user_id/items/:item_id');
app.use('api/v1', router);

Categories