Can you extend controller in Nodejs/Express? - node.js

I've a route A that uses controller 1.
#A
router.get("/:id", require("./user_get"));
I want to use same logic for route B but only serve different html.
#B
router.get("/:id", ????);
I could copy paste code from user_get to new file. But I was wondering is there a way to extend it. Can I change res.render value somehow if I simply include user_get in new file.

What you want to do is separate out whatever action you're doing in userGet into a middleware.
const userGet = (req, res, next) => {
// whatever you use to get the user
// if your user getting is async the following lines would of course need to be in your `.then`
res.locals.user = user
next()
)}
Notice the middleware signature -- a function with req, res, and next, the use of res.locals, and the call to next at the end.
Then you'd write your route handlers:
const routeOneHandler = (req, res) => {
res.render('templateForRouteOne', { user: res.locals.user })
}
const routeTwoHandler = (req, res) => {
res.render('templateForRouteTwo', { user: res.locals.user })
}
and update your routes like so:
// route one
router.get('/:id', userGet, routeOneHandler)
// route two
router.get('/:id', userGet, routeTwoHandler)
If the concept of middleware and next is new to you, I recommend checking out the Express documentation, or see the info in this answer.

Related

Express: Add middleware on static folder

Hello I have express app where i need to plug a custom middleware/logic before accessing static folder for serving files, how i can achieve that without applying this middleware on every route. For now code looks like:
function middleware() {
console.log('hello');
}
app.use(middleware).use(express.static('public'));
app.listen(8000, () => {
console.log('server running on 8000');
});
app.get('/hi', (req, res) => {});
Issue is when hi is called middleware is executed as well and I want to execute it only if static files from public folder are called
first of all create a middleware.js in the middleware you can impalement business logic
module.exports = (req, res, next) => {
let substring = ".html"; // static files
if (req.originalUrl.includes(substring)) {
console.log("hello");
}
next();
};
in app.js
const middleware = require('./middleware');
app.use(middleware).use(express.static('public'));
app.listen(8000, () => {
console.log('server running on 8000');
});
app.get('/hi', (req, res) => {});
I see 2 options:
either your middleware first checks that the requested file exists
app.get('*', myMiddleware, express.static('public'))
or you run your middleware and static middleware if the path LOOKS like the static
app.get('*(png|jpg|css)', myMiddleware, express.static('public'))
In both cases you can chain middlewares together (note multiple params in app.get). So that they are executed in this order and you can control the chain by using next() and next('route')
Your middleware would look like this:
function myMiddleware (req, res, next) {
// optionally check if file exists depending if you take option 1
if (/* file exist check based on req.path */) {
// if file doesn't exit, go to next router
next('route') // here 'route' is a magic word, see https://expressjs.com/en/guide/using-middleware.html
}
console.log('hi')
next() // don't forget this!
}
NOTE: next('route') in option 1 only works with app.get not simple app.use, but i hope you only need GET for your static

How to pass one of multiple middleware nodejs

I have a router like this
router.post("/roomplayers",[authjwt.verifyTokenAdmin,authjwt.finishedRoomManagement,authjwt.activeRoomManagement],
RoomController.findPlayers)
and I would to get the controller RoomController.findPlayers if the admin have this cofinishedRoomManagementdepermission
OR this activeRoomManagement
How can I do that
If your question is how to pass through many middleware's in your route the solution is below.
const tokenMiddleWare = (req, res, next) =>{
//Your code here
next();
}
const isAdminMiddleWare = (req, res, next)=>{
//Your code here
next();
}
So now that we have two middlewares and one controller(I omitted it though) now you can work on route and pass those middlewares and controller but before I start I want point something important
So with the next you want to push the user down out of the middleware driving them closer to the route that they want to hit only when they meet all you validation that's when you want to push them down
router.post('/api/login', tokenMiddleWare, isAdminMiddleWare, (req, res)=>{
authController.login(req, res);
})
Now this would be how you pass down multiple middlewares and using your controller

forwarding to another route handler without redirecting in express

I have the following code :
app.get('/payment', function(req, res) {
// do lots of stuff
});
now I want to add the following :
app.post('/payment', function(req, res) {
req.myvar = 'put something here';
// now do the same as app.get() above
});
Obviously I want to reuse the code. I tried doing next('/payment') inside the post handler and put it above the get handler, but no luck, probably because they are different VERBs.
What are my options ?
Thanks.
Just lift out the middleware to its own function and use it in both routes.
function doLotsOfStuff (req, res) {
// do lots of stuff
}
app.get('/payment', doLotsOfStuff);
app.post('/payment', function(req, res, next) {
req.myvar = 'put something here';
next();
}, doLotsOfStuff);

Node.js matching the url pattern

I need an equivalent of following express.js code in simple node.js that I can use in middleware. I need to place some checks depending on the url and want to do it in a custom middleware.
app.get "/api/users/:username", (req,res) ->
req.params.username
I have the following code so far,
app.use (req,res,next)->
if url.parse(req.url,true).pathname is '/api/users/:username' #this wont be true as in the link there will be a actual username not ":username"
#my custom check that I want to apply
A trick would be to use this:
app.all '/api/users/:username', (req, res, next) ->
// your custom code here
next();
// followed by any other routes with the same patterns
app.get '/api/users/:username', (req,res) ->
...
If you only want to match GET requests, use app.get instead of app.all.
Or, if you only want to use the middleware on certain specific routes, you can use this (in JS this time):
var mySpecialMiddleware = function(req, res, next) {
// your check
next();
};
app.get('/api/users/:username', mySpecialMiddleware, function(req, res) {
...
});
EDIT another solution:
var mySpecialRoute = new express.Route('', '/api/users/:username');
app.use(function(req, res, next) {
if (mySpecialRoute.match(req.path)) {
// request matches your special route pattern
}
next();
});
But I don't see how this beats using app.all() as 'middleware'.
You can use node-js url-pattern module.
Make pattern:
var pattern = new UrlPattern('/stack/post(/:postId)');
Match pattern against url path:
pattern.match('/stack/post/22'); //{postId:'22'}
pattern.match('/stack/post/abc'); //{postId:'abc'}
pattern.match('/stack/post'); //{}
pattern.match('/stack/stack'); //null
For more information, see: https://www.npmjs.com/package/url-pattern
Just use the request and response objects as you would in a route handler for middleware, except call next() if you actually want the request to continue in the middleware stack.
app.use(function(req, res, next) {
if (req.path === '/path') {
// pass the request to routes
return next();
}
// you can redirect the request
res.redirect('/other/page');
// or change the route handler
req.url = '/new/path';
req.originalUrl // this stays the same even if URL is changed
});

Forward request to alternate request handler instead of redirect

I'm using Node.js with express and already know the existence of response.redirect().
However, I'm looking for more of a forward() functionality similar to java that takes the same parameters as redirect, but internally forwards the request instead of having the client perform the redirect.
To clarify, I am not doing a proxy to a different server. I'd like to forward('/other/path') directly within the same app instance
It wasn't apparently obvious how to do this from the express documentation. Any help?
You just need to invoke the corresponding route handler function.
Option 1: route multiple paths to the same handler function
function getDogs(req, res, next) {
//...
}}
app.get('/dogs', getDogs);
app.get('/canines', getDogs);
Option 2: Invoke a separate handler function manually/conditionally
app.get('/canines', function (req, res, next) {
if (something) {
//process one way
} else {
//do a manual "forward"
getDogs(req, res, next);
}
});
Option 3: call next('route')
If you carefully order your router patterns, you can call next('route'), which may achieve what you want. It basically says to express 'keep moving on down the router pattern list', instead of a call to next(), which says to express 'move down the middleware list (past the router)`.
You can implement forward (aka rewrite) functionality by changing request url property and calling next('route').
Note that the handler performing forward needs to be configured before other routes which you perform forwards to.
This is example of forwarding all *.html documents to routes without .html extension (suffix).
function forwards(req, res, next) {
if (/(?:.+?)\.html$/.test(req.url)) {
req.url = req.url.replace(/\.html$/, '');
}
next('route');
}
You call next('route') as the last operation. The next('route') passes control to subsequent routes.
As mentioned above, you need to configure forwards handler as one of the first handlers.
app.get('*', forwards);
// ...
app.get('/someroute', handler);
The above example will return the same content for /someroute as well as /someroute.html. You could also provide an object with a set of forward rules ({ '/path1': '/newpath1', '/path2': '/newpath2' }) and use them in forward mechanism.
Note that regular expression used in forwards function is simplified for mechanism presentation purposes. You would need to extend it (or perform check on req.path) if you would like to use querystring parameters etc.
I hope that will help.
For Express 4+
Using the next function does not work if the next handler is not added in the right order. Instead of using next, I use the router to register the handlers and call
app.get("/a/path", function(req, res){
req.url = "/another/path";
app.handle(req, res);
}
Or for HTML5 mode of React/Angular
const dir = process.env.DIR || './build';
// Configure http server
let app = express();
app.use('/', express.static(dir));
// This route sends a 404 when looking for a missing file (ie a URL with a dot in it)
app.all('/*\.*', function (req, res) {
res.status(404).send('404 Not found');
});
// This route deals enables HTML5Mode by forwarding "missing" links to the index.html
app.all('/**', function (req, res) {
req.url = 'index.html';
app.handle(req, res);
});
Using the next function does not work if the next handler is not added in the right order. Instead of using next, I use the router to register the handlers and call
router.get("/a/path", function(req, res){
req.url = "/another/path";
router.handle(req, res);
}
Express 4+ with nested routers
Instead of having to use the outside of route/function app, you can use req.app.handle
"use strict";
const express = require("express");
const app = express();
//
// Nested Router 1
//
const routerOne = express.Router();
// /one/base
routerOne.get("/base", function (req, res, next) {
res.send("/one/base");
});
// This routes to same router (uses same req.baseUrl)
// /one/redirect-within-router -> /one/base
routerOne.get("/redirect-within-router", function (req, res, next) {
req.url = "/base";
next();
});
// This routes to same router (uses same req.baseUrl)
// /one/redirect-not-found -> /one/two/base (404: Not Found)
routerOne.get("/redirect-not-found", function (req, res, next) {
req.url = "/two/base";
next();
});
// Using the full URL
// /one/redirect-within-app -> /two/base
routerOne.get("/redirect-within-app", function (req, res, next) {
req.url = "/two/base";
// same as req.url = "/one/base";
//req.url = req.baseUrl + "/base";
req.app.handle(req, res);
});
// Using the full URL
// /one/redirect-app-base -> /base
routerOne.get("/redirect-app-base", function (req, res, next) {
req.url = "/base";
req.app.handle(req, res);
});
//
// Nested Router 2
//
const routerTwo = express.Router();
// /two/base
routerTwo.get("/base", function (req, res, next) {
res.send("/two/base");
});
// /base
app.get("/base", function (req, res, next) {
res.send("/base");
});
//
// Mount Routers
//
app.use("/one/", routerOne);
app.use("/two/", routerTwo);
// 404: Not found
app.all("*", function (req, res, next) {
res.status(404).send("404: Not Found");
});
app.get('/menzi', function (req, res, next) {
console.log('menzi2');
req.url = '/menzi/html/menzi.html';
// res.redirect('/menzi/html/menzi.html');
next();
});
This is my code:when user enter "/menzi",the server will give the page /menzi/html/menzi.html to user, but the url in the browser will not change;
You can use run-middleware module exactly for that. Just run the handler you want by using the URL & method & data.
https://www.npmjs.com/package/run-middleware
For example:
app.runMiddleware('/get-user/20',function(code,body,headers){
res.status(code).send(body)
})

Resources