I have this middleware:
app
.use('/:lang?', middleware.setLanguage)
.use('/thanks/:lang?', middleware.setLanguage)
.use('/forum/:lang?', middleware.setLanguage);
I want use a function called setLanguage in every route that have lang?, and currently this code is working, but i can believe dry that.
Someone knows how to? I search in the documentation, but didn't find anything..
Use .param to process the parameters up front so that you don't have to do it on every route that uses it. For example, here's one that starts building a query with moongoose for a product crud:
app.param('product_id', function (req, res, next, product_id) {
req.product_id = new ObjectId(product_id);
req.product = Product.findOne(req.product_id);
next();
});
// update product
app.put('/products/:product_id', function (req, res) {
Product.findOneAndUpdate(req.product_id, req.body, function (err, product) {
res.json(product.toObject());
});
});
// get product by id
app.get('/products/:product_id', function (req, res) {
req.product.lean().exec(function (err, product) {
res.json(product);
});
});
None of my routes have to get the product id and convert it to an ObjectId because i did that up front for all routes with .param.
Related
I have the following case:
There is a list of routes in the form
var lst = ["route1/:foo", "route2/:bar", "route3/:bar/route4/:baz", ..] // this list has like 200 entries
I have the following code
app.use(lst, function (req, res) {
// here I want to know which route the middleware was invoked on
// req.route.path doesn't work unless I use app.METHOD()
// req.originalUrl would give me route1/200, instead of route1/:foo
})
What I tried so far:
Using the router stack as in app._router.stack, my routes aren't even registered there - I don't see route1/:foo, route2/:bar and route3/:bar/route4/:baz
Hook into the express router:
var Route = express.Route;
let defaultImplementation = Route.prototype.dispatch;
function foo(req, res) {
console.log('Called route ', req.route.path); // still doesn't trigger on the routes in lst, only parent routes
}
Route.prototype.dispatch = function handle(req, res, next) {
foo(req, res); // req.route is available here
defaultImplementation.call(this, req, res, next);
};
By the way, I'm passing those routes and using them along with http-proxy-middleware https://github.com/chimurai/http-proxy-middleware, so if you have any clues on how do achieve that with that library as well, I'd be very grateful as I couldn't find out.
I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.
I have Posts that are grouped into Categories, each in its own collection.
I filter posts by category: all posts in the Sport category are displayed at http://localhost:3000/sport, all posts in the Travel category are displayed at http://localhost:3000/travel.
For this purpose I introduced the route router.get('/:catname', postsController.getPostsByCategory);:
const express = require('express');
const postsController = require('../../controllers/front-end/posts');
// Express router
const router = express.Router();
// Get Posts
router.get('/', postsController.getPosts);
// Get Posts by Category
router.get('/:catname', postsController.getPostsByCategory);
// Get Single Post
router.get('/:id', postsController.getSinglePost);
module.exports = router;
Categories do get filtered by category, but router.get('/:catname', postsController.getPostsByCategory); and router.get('/:id', postsController.getSinglePost); are conflicting in such a way that single posts are no longer displayed.
That is because posts IDs are taken for category routes.
In the controller I have the 2 methods getPostsByCategory and getSinglePost:
exports.getPostsByCategory = async (req, res, next) => {
function titleize(slug) {
var words = slug.split("-");
return words.map(function(word) {
return word.charAt(0).toUpperCase() + word.substring(1).toLowerCase();
}).join(' ');
}
const postCategory = new RegExp(titleize(req.params.catname),"ig");
const singleCategory = await Category.findOne({cat_name:postCategory})
const posts = await Post.find({ category : singleCategory }, (err, posts) => {
if (err) {
console.log('Error: ', err);
} else {
res.render('default/index', {
moment: moment,
layout: 'default/layout',
website_name: 'MEAN Blog',
page_heading: 'XPress News',
page_subheading: 'A MEAN Stack Blogging Application',
posts: posts.reverse(),
});
}
}).populate('category');
};
exports.getSinglePost = (req, res, next) => {
let id = req.params.id;
if (id.match(/^[0-9a-fA-F]{24}$/)) {
Post.findById(id, function(err, post) {
if (err) {
console.log('Error: ', err);
} else {
res.render('default/singlepost', {
layout: 'default/layout',
website_name: 'MEAN Blog',
post: post
});
}
});
}
};
In the view for (all) the posts I have:
<p class="post-meta">Posted in <%= post.category.cat_name %>, on <%= moment(post.created_at).format( 'MMM-DD-YYYY') %></p>
I would like to fix this issue in a manner that keeps the pattern http://localhost:3000/category-name for the URLs that display posts in categories.
How can I achieve that?
If it is important to you to keep the pattern http://localhost:3000/:param both for single post and post by Category, you can use only one route method handling both requests
// This will handle Get requests both for Posts by Category and single Post
router.get('/:param', (req, res, next) => {
// If :param is Mongo ObjectId call the getSinglePost method.
if (req.params.param.match(/^[0-9a-fA-F]{24}$/)) {
postsController.getSinglePost(req, res, next);
}
else {
postsController.getPostsByCategory(req, res, next);
}
});
I just added a middleware, which check the request parameter. If it's type of ObjectId, you call getSinglePost method, otherwise call getPostsByCategory method.
The only you have to do is to replace req.params.catname and req.params.id with req.params.param inside both methods of your postsController file.
You can also remove the match check in the getSinglePost method, since it is implemented in the route file.
Well these both are actually the same route:
// Get Posts by Category
router.get('/:catname', postsController.getPostsByCategory);
// Get Single Post
router.get('/:id', postsController.getSinglePost);
I would suggest to create different routes for example:
// Get Posts by Category
router.get('/category/:catname', postsController.getPostsByCategory);
// Get Single Post
router.get('/post/:id', postsController.getSinglePost);
I am using Express.js as http server. Defined all my routes.
Most endpoints need to verify session before returning a response. E.g. below code serves users in the system and list of services respectively:
function getUsers(req, res, next) {
verifyUser(req, res, next, function () {
//serve users
});
}
function getServices(req, res, next) {
verifyUser(req, res, next, function () {
//serve services
});
}
You probably noticed there is a verifyUser function which validates the session. Which is as below.
function verifyUser(req, res, next, callback) {
var sessionKey = req.cookies.sessionKey;
var user = users.userBySession(sessionKey);
if (user) {
callback(req, res, next, user);
} else {
res.status(401).send({
message: 'Unauthorized'
});
}
}
As you can see I keep passing in req, res and next parameters along with a callback whenever I use this function.
I tried to use apply function to make it easier. Changed my getUsers function like this:
function getUsers(req, res, next) {
verifyUser
.apply(null, arguments, function () {
//serve users
});
}
The problem with this approach is callback is not passed into verifyUser function. And I don't really like passing null as scope with each call.
How can I achieve this by writing less and better code ? Any ideas?
You could use bind to create a 'partial function':
// create bound responseHelper object
var responseHelper = verifyUser.bind(null, req, res, next);
// usage
responseHelper(getUsersCallback); // same as verifyUser(req, res, next, getusersCallBack);
I think you're looking to turn verifyUser into a middleware function.
function verifyUser (req, res, next) {
var user = // yadda yadda session stuff
if (user) {
req.user = user; // [1] what you do to the req object here...
} else {
return res.status(401).send({ message: "No way Smokey Joe"});
/**
* alternatively, do something like
* var err = new Error("Not authorized");
* err.statusCode = 401;
* return next(err);
*
* this will kick off Express' error handling mechanism,
* which you should read about in the docs (see the link below)
*/
}
next();
// very important to call next after this verifyUser has done its job
// if you don't, the next middleware won't go off,
// and the request will just hang
}
function getUsers (req, res, next) {
// [2] will show up on the req object here, assuming you chain these
// two functions together as middleware
}
app.get("/users", verifyUser, getUsers);
app.get("/services", verifyUser, getServices);
// here's a route that needs no session auth, so no need to verifyUser
app.get("/latest-posts", getLatestPosts);
When you tell Express to use a function or attach a function to a route path via get('/my/route', hanlderFun) or some such, you've basically turned handlerFun into a middleware.
You can define however many middleware as handlers on a route as you like, and they'll all execute in turn as long as you keep calling next.
app.post("/checkout", verifyUser, tallyCart, checkInventory, doPayment, sendInvoice);
The job of next is to pass control from the current middelware to the next one. It's an object
You can do other stuff with next, too, which you should read up on in the docs.
http://expressjs.com/en/guide/writing-middleware.html
http://expressjs.com/en/guide/using-middleware.html
The docs on routing have good info on middleware as well:
http://expressjs.com/en/guide/routing.html
For extra credit, check out error handling middleware, too:
http://expressjs.com/en/guide/error-handling.html
I have two resources, employees and employee groups. I'm trying to implement a nice URL structure like:
GET /employees List employees.
GET /employees/123 Get employee 123.
GET /employees/groups List employee groups.
GET /employees/groups/123 Get employee group 123.
Using ExpressJS I have:
router.get('/employees', (req, res, next) => { next(); });
router.get('/employees/:id', (req, res, next) => { next(); });
router.get('/employees/groups', (req, res, next) => { next(); });
router.get('/employees/groups/:id', (req, res, next) => { next(); });
router.all('*', (req, res) => { res.send('...'); });
This doesn't work, because Express can't tell the difference between /employees/:id and /employees/groups. It thinks groups is an id because /employees/:id comes first.
I did have URL's like:
GET /employees
GET /employees/123
GET /employees-groups
GET /employees-groups/123
Which works, but doesn't have the nice resource/sub-resource format. The groups are groups of employees and so I'd like the URL's to match that.
If I were getting the groups for an employee it would be fine (/employees/:id/groups), but I'm getting all groups, which are employee groups.
How could I set up Express routes to route properly while still keeping the URL structure I want..?
I guess I need a way for Express to distinguish between an id and a sub-resource. Is there any way to do that..?
UPDATE
I obviously should've said that I'm using next() in each handler, because I need Express to move onto another middleware function, one that controls the response of all requests. It's this other middleware function that actually sends a response. So I need:
Handler for the route.
Handler for all requests.
Express searches for the first route that matches and handles it with the provided function.
Try the other way around:
router.get('/employees', (req, res) => {});
router.get('/employees/groups', (req, res) => {});
router.get('/employees/groups/:id', (req, res) => {});
router.get('/employees/:id', (req, res) => {});
Now express will work its way trough the routes, '/employees/123' will only match on the last route, so that one will be used by express. '/employees/groups' will be matched sooner by the second route and that one will be used.
Very simple but these things can cost you some time figuring out.
RobbyD set me on the right track. This is what I've ended up with:
index.js
router.all('*', setupHandler);
router.get('/employees', getEmployees);
router.get('/employees/groups', getGroups);
router.get('/employees/groups/:id', getGroup);
router.get('/employees/:id', getEmployee);
router.use(errorHandler);
setupHandler()
function setupHandler(req, res, next) {
res.locals.standardRes = {
"some": "data"
};
res.locals.doResponse = (res) => {
// ...
res.json(res.locals.standardRes);
};
next();
}
getEmployees()
function getEmployees(req, res, next) {
somethingThatReturnsAPromise().then(data => {
// add to res.locals.standardRes here
res.locals.doResponse(res);
}).catch(err => {
next(err);
});
}
errorHandler()
function errorHandler(err, req, res, next) {
console.log('err', err);
// add to res.locals.standardRes here
// set correct res.status here
res.locals.doResponse(res);
}
So the handlers are in the order in RobbyD's answer. I've used res.locals to hold a response function (doResponse(res)) to call from each handler. If there's an error I call next(err) as normal to move to errorHandler().
I guess it's all about getting the right flow from middleware to middleware and sending the response at the right time.
I had like 100 blogs on "/" page and also a link to Sortby Date:a-z When I click
these link I m transferred to different routes one is "/sort_by_date" and other is "/sort_alphabetically".I want this sorting to appear on "/".I m not able to do it on "/" page that is whay I had specified to different routes.I want this sorting to appear on "/" page by clicking to differnt links different sorting should be appear.This whole application is written in nodejs Mongoose express.
The homepage of the blog
router.get('/', function (req, res) {
var q= blog.find({}).limit(100);
q.exec(function(err,docs)
{
res.render('blog',{"no_of_blogs":docs
,"in_ca":true })
});
});
The page which is sorting by date
router.get('/sort_by_date', function (req, res) {
blog.find({},{sort:{date:-1}},function (err, docs) {
res.render('index_date_blog',{"no_of_blogs":docs
,"in_ca":true })
});
});
This is the page sorted by alphabetically
router.get('/sort_alphabetically', function (req, res) {
blog.find({},{sort:{title}},function (err, docs) {
res.render('index_date_blog',{"no_of_blogs":docs
,"in_ca":true })
});
});
Thanks in advance.
Use the query string to pass in the sort variable.
Make your links to change the sort link to ?sort=title or ?sort=date
Note, below code is not tested, but should help you along the way:
router.get('/', function (req, res) {
var sortQuery = req.query.sort;
var sort = {};
if(sortQuery=='date')
sort={date:-1}
if(sortQuery=='title')
sort={title: 1}
var q= blog.find({}).limit(100).sort(sort);
q.exec(function(err,docs)
{
res.render('blog',{"no_of_blogs":docs ,"in_ca":true })
});
});