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
Related
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.
I am using keystone and I have productDetail route in which I can add variables in res.locals to be used in templates. Is there a way I can use res.locals (of route file) in middleware.js file? As right now middleware is executing before route, I want route file to be executed first.
This is where middleware is executing in index.js file
keystone.pre('routes', middleware.initLocals);
And after that we have
exports = module.exports = function(app) {
// Views
app.get('/', routes.views.index);
app.get('/product-detail/:product', routes.views.productDetails);
}
I'm not sure if I got your question but this might help. You can run as many custom middleware you want after the middleware.initLocals (which apparently runs first). In your routes/middleware.js file, you can have, for example, two middleware:
exports.middleware0 = function (req, res, next) {
// Do some stuff
next();
};
exports.middleware1 = function (req, res, next) {
// Do some other stuff
next();
};
Then, inside your routes/index.js you can chain middleware together:
//...
var middleware = require('./middleware');
//...
exports = module.exports = function (app) {
// Use the middleware0 and middleware1:
app.get('/product-detail/:product', [middleware.middleware0, middleware.middleware1], routes.views.productDetails);
};
I'm using node and express to create a rest api. I followed a tutorial where all the routes and its logic are saved in a routes.js file like this:
SERVER JS:
var express = require('express');
var app = express();
(...)
require('./app/routes.js')(app, port, express);
ROUTES.JS
module.exports = function(app, port, express) {
var apiRoutes = express.Router();
(...)
//Sample route
apiRoutes.get('/userfiles', function(req, res) {
UserFile.find({ owner: req.decoded.user.email }, function(err, filesList) {
if (err)
return done(err);
res.json({ success: true, files: filesList });
});
});
My problem is twofold:
1 - Routes can easily contain code thats 150 lines long, some of them far longer. It doesn't feel clean to have route declarations and the logic grouped together. Is it a good practice to do something like this instead?
apiRoutes.post('/randomRoute', function(req, res) {
return res.json(functionThatContainsTheActualCode(req));
});
(and then have an functionThatContainsTheActualCode function with all the logic in a different file).
2 - I have middleware that applies to some functions (for example, some routes are only accessible for logged in users and those routes go through an authentication middleware). Currently way I do it is declaring public routes before the middleware declaration and private routes after, which feels incredibly hacky. How can I separate public and private routes (and the middleware itself) in different files?
Problem 1:
We need to go deeper.
Change the route file to just require the actual router logic.
routes.js
// where app = express();
module.exports = (app) => {
// index.js happens to be a file exporting the router.
app.use('/', require('./index'));
// this is basically the idea. Create a separate file for the actual logic.
app.use('/route', require('.path/to/file'));
};
and in file.js
const express = require('express'),
router = express.Router();
router.verb('/path/', (req, res, next) => {
// do whatever
});
// this is required
module.exports = router;
Problem 2:
Middleware is basically a function taking in request, response, next as 3 params, doing something with the request and either sending out a response or moving on to the next middleware. That's why you need to call next if you want to move to next middleware in the chain.
Now all you need is a file that exports a function which takes request, response, next as params.
// lets call this auth.js
module.exports = function(req, res, next) {
// do logic
if () {
return res.send(); // or res.somethingThatSendsOutAHttpResponse()
}
// next middelware
next();
};
Since express routes are also middlewares, (mind blown), you can mount them top down.
To authenticate a route, just put the auth.js middleware on top of that route.
router.get('/', require('./auth'));
router.get('/', require('./log'));
router.get('/', (req, res, next) => {
// yolo
});
Now since this is web dev, you still got problems.
Now all your boring database queries are scattered everywhere.
Fear not, you can solve it, by, guess, creating another file.
apiRoutes.get('/userfiles', function(req, res) {
const userFile = require('/path/to/model/with/userfile/methods/exported/out');
// do something with userFile's methods
});
I have a few middlewares that I want to combine into one middleware. How do I do that?
For example...
// I want to shorten this...
app.use(connect.urlencoded())
app.use(connect.json())
// ...into this:
app.use(combineMiddleware([connect.urlencoded, connect.json]))
// ...without doing this:
app.use(connect.urlencoded()).use(connect.json())
I want it to work dynamically -- I don't want to depend on which middleware I use.
I feel like there's an elegant solution other than a confusing for loop.
Express accepts arrays for app.use if you have a path:
var middleware = [connect.urlencoded(), connect.json()];
app.use('/', middleware)
However, if you want a generic combineMiddleware function, you can build a helper easily without any additional libraries. This basically takes advantage of the fact that next is simply a function which takes an optional error:
/**
* Combine multiple middleware together.
*
* #param {Function[]} mids functions of form:
* function(req, res, next) { ... }
* #return {Function} single combined middleware
*/
function combineMiddleware(mids) {
return mids.reduce(function(a, b) {
return function(req, res, next) {
a(req, res, function(err) {
if (err) {
return next(err);
}
b(req, res, next);
});
};
});
}
If you like fancy stuff, here is one of possible solutions:
var connect = require('connect')
var app = connect()
function compose(middleware) {
return function (req, res, next) {
connect.apply(null, middleware.concat(next.bind(null, null))).call(null, req, res)
}
}
function a (req, res, next) {
console.log('a')
next()
}
function b (req, res, next) {
console.log('b')
next()
}
app.use(compose([a,b]))
app.use(function (req, res) {
res.end('Hello!')
})
app.listen(3000)
Here is what it does: compose function takes array of middleware and return composed middleware. connect itself is basically a middleware composer, so you can create another connect app with middlewares you want: connect.apply(null, middleware). Connect app is itself a middleware, the only problem is that it doesn't have a next() call in the end, so subsequent middleware will be unreachable. To solve that, we need another last middleware, which will call next : connect.apply(null, middleware.concat(last)). As last only calls next we can use next.bind(null, null) instead. Finally, we call resulting function with req and res.
Old question, but the need is still frequent for all the things using middlewares, like connect, express or custom made req/res/next patterns.
This is a very elegant and purely functional solution:
File ./utils/compose-middleware.js:
function compose(middleware) {
if (!middleware.length) {
return function(_req, _res, next) { next(); };
}
var head = middleware[0];
var tail = middleware.slice(1);
return function(req, res, next) {
head(req, res, function(err) {
if (err) return next(err);
compose(tail)(req, res, next);
});
};
}
module.exports = compose;
The final result of the compose(middlewareList) is a single middleware that encapsulates the whole chain of middleware initially provided.
Then simply import it and use like this:
File app.js:
var connect = require('connect');
var compose = require('./utils/compose-middleware');
var middleware = compose([
connect.urlencoded(),
connect.json()
]);
var app = connect();
app.use(middleware);
A simple and native way, and you don't need to install anything.
const {Router} = require('express')
const combinedMiddleware = Router().use([middleware1, middleware2, middleware3])
Then you can use the combinedMiddleware where you want. For example, you may want to run different set of middlewares/handlers for the same route depending on some conditions (a request attributes, for example):
app.get('/some-route', (req, res, next) => {
req.query.someParam === 'someValue'
? combinedMiddleware1(req, res, next)
: combinedMiddleware2(req, res, next)
})
If you're willing to use a library:
https://www.npmjs.org/package/middleware-flow
var series = require('middleware-flow').series;
var app = require('express')();
app.use(series(mw1, mw2, mw2)); // equivalent to app.use(mw1, mw2, mw3);
Make a list and use a loop.
const connect = require('connect')
const { urlencoded, json } = require('body-parser')
const app = connect()
[ urlencoded(), json() ].forEach(app.use, app)
The second argument of .forEach is used for this, but if you like you can also do the same with:
[ urlencoded(), json() ].forEach(app.use.bind(app))
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)
})