in express.js, any way to capture request to both json and html in one function?
Asked Answered
O

3

25

Does anybody know a way in express.js to capture requests in a single function for both html and json?

Essentially I want a single route for both /users and /users.json - like rails does with its routes -> controller.

That way, I can encapsulate the logic in a single function and decide to render either html or json.

Something like:

app.get('/users[.json]', function(req, res, next, json){
  if (json)
    res.send(JSON.stringfy(...));
  else
    res.render(...); //jade template
});

Could I use a param perhaps?

Oesophagus answered 19/1, 2012 at 15:30 Comment(2)
Why not one route with content negotiation? (Said in the voice of Zoidberg.)Ragged
author of express recommends you use content negotiation: github.com/visionmedia/express/issues/1340Tailback
I
20

A route is simple a string which is compiled to a RegExp internally, as the manual says, so you can do something like this:

app.get("/users/:format?", function(req, res, next){
  if (req.params.format) { res.json(...); }
  else {
    res.render(...); //jade template
  }
});

Check more here: http://expressjs.com/guide.html#routing

Insensibility answered 19/1, 2012 at 15:48 Comment(6)
ah right - found the answer in the express guide. app.get('/users.:format?', function(req, res){ if (req.params.format == 'json') res.send(JSON.stringify(...)); else res.render(...); //jade template });Oesophagus
i just found it at the same time - but appreciate the response.Oesophagus
in case anyone runs into this issue like I did, make sure your route is defined as /users.:format? and not /users:format? if you want to differentiate users vs users.json. otw you will have to specify users/json.Hunker
@Oesophagus Note that it's JSON.stringify, not JSON.stringfy.Orson
if I have route defined as '/users/:userId.:format' it doesn't catch request without specifying extension e.g '/users/123' . Is it possible to catch this request by the same route?Harbinger
Normally if you want some placeholder to be optional just add a question mark "?", but in your example there's also a dot '.' in there. You can always default to a regular expression instead of a regular "placeholdery" Express route.Insensibility
C
55

I believe res.format() is the way to do this in Express 3.x and 4.x:

res.format({
  text: function(){
    res.send('hey');
  },

  html: function(){
    res.send('<strong>hey</strong>');
  },

  json: function(){
    res.send({ message: 'hey' });
  }
});

This relies on the Accept header, however you can munge this header yourself using a custom middleware or something like connect-acceptoverride.

One example of a custom middleware might be:

app.use(function (req, res, next) {
  var format = req.param('format');

  if (format) {
    req.headers.accept = 'application/' + format;
  }

  next();
});
Chesterchesterfield answered 20/10, 2012 at 2:29 Comment(3)
this works but requires the client to set the Accept header. much simpler to handle it as he mentions in the comments above.Hunker
@emilebaizel: responded to your comment in my recent edit. :)Chesterchesterfield
This should be the accepted answer. It's up to the client to ask for the proper header, not the server to figure it out.Silicium
I
20

A route is simple a string which is compiled to a RegExp internally, as the manual says, so you can do something like this:

app.get("/users/:format?", function(req, res, next){
  if (req.params.format) { res.json(...); }
  else {
    res.render(...); //jade template
  }
});

Check more here: http://expressjs.com/guide.html#routing

Insensibility answered 19/1, 2012 at 15:48 Comment(6)
ah right - found the answer in the express guide. app.get('/users.:format?', function(req, res){ if (req.params.format == 'json') res.send(JSON.stringify(...)); else res.render(...); //jade template });Oesophagus
i just found it at the same time - but appreciate the response.Oesophagus
in case anyone runs into this issue like I did, make sure your route is defined as /users.:format? and not /users:format? if you want to differentiate users vs users.json. otw you will have to specify users/json.Hunker
@Oesophagus Note that it's JSON.stringify, not JSON.stringfy.Orson
if I have route defined as '/users/:userId.:format' it doesn't catch request without specifying extension e.g '/users/123' . Is it possible to catch this request by the same route?Harbinger
Normally if you want some placeholder to be optional just add a question mark "?", but in your example there's also a dot '.' in there. You can always default to a regular expression instead of a regular "placeholdery" Express route.Insensibility
C
1

I was not happy with the above answers. Here's what I did instead. Please vote up if it helps you.

I just make sure that all json requests have the Content-Type header set to 'application/json'.

if (req.header('Content-Type') == 'application/json') {
  return res.json({ users: users });
} else {
  return res.render('users-index', { users: users });
}
Circumscription answered 9/3, 2017 at 23:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.