Content Negotiation in Node JS, augmenting Express JS
Others have written about content negotiation. It’s use, implementation, purpose. I think they are all valid. But I feel like I’m alone when it comes to an idea that seems so simple and powerful to me. I’m quite surprised that others aren’t in alignment with me on this, or if they are, I haven’t connected with them. I feel like every web site should be built to negotiate content and respond with the requested representation for a resource. I picked those words carefully. So I’m going to write it again in a block quote.
Every web site should respond with the requested representation for a resource.
Let’s cut through the chase. This is what I mean.
- https://content-negotiation.glitch.me/index.xml
- https://content-negotiation.glitch.me/index.json
- https://content-negotiation.glitch.me/index.phtml
- https://content-negotiation.glitch.me/index.html
- www.reddit.com/controversial.xml
See what I did there? I requested a specific representation in every link. XML, JSON, pHTML (Partial HTML/HTML fragment). I said, “I want the index for a web site”, but please give it to me in HTML. Crap! I don’t want the full document, how ‘bout just a fragment of the HTML (partial HTML). Oh, wait, I need JSON because I want to show it on another page. Whew, thanks.
When I’m building out a web site, sometimes I just want the same resource but in a different representation. I might just want it in JSON so I can make an AJAX request and display a login form anytime you click on the login link anywhere on the site. Especially in the scenario where the site is already built out and this new UX is an afterthought. Or some other product team just wants to show the product info in their app, but they just want the partial HTML, not the full HTML document. What was that you said? You want to use your RSS reader to view my site? Ok, no worries, just go to https://content-negotiation/index.xml and you’ll get what you asked for.
It’s so clear to me that this a great way to build a web site (service, really). I’m not saying it’s the only way, but if you’re going to build web sites, this is a great feature to include by default, as a first class citizen.
Ok, you’re ready to do it. I’ve convinced you. You’ve come to the dark side;) Yayayayayayayaya! Now what? Well, there’s a few concepts that stand out when I built this functionality. I’ll just list them.
- Content Negotiation
- Encapsulate what varies: Views change A LOT, so encapsulate the views from the routing logic
Ok, just 2. But they’re key. First one is Content Negotiation. Your site should negotiate with the client with what content to respond. In my mind, this means with what representation to respond. Content can be represented as an image like GIF or PNG, and HTML or JSON. But I’m not really talking about what image type the client can accept. While that might be cool to say oh, you want an image? What type can you accept? a GIF, ok here’s the resource as a GIF. That is reallycool. But representations like HTML, JSON, XML, and pHTML are more useful to me as a web developer when agregrating content for a view.
Second is the idea that views change A LOT. What I mean is that in a web sites life, the views are the things that change more often than the server side code and logic. There’s a book called Head First Design Patterns where I first read the notion to “encapsulate what varies”. Working at True.com at that time, it really rang true to me because we changed the sign up pages and payment pages EVERY WEEK (this was circa 2007)! So much so that I came to believe that views are throw away and I ventured out to find a way to encapsulate the views from the rest of the code base because I knew we were going to build new ones every week and throw them away.
Ok, so I lured you here to find out how to do content negotiation in Node JS and I suggested that I augmented Express JS while doing it. But Express JS already has the facility to do this, you say. And so it does. In your Express JS routing logic, you can write code like:
app.get('/', function(req, res, next){
res.format({
html: function(){
res.send('<ul>' + users.map(function(user){ return '<li>' + user.name + '</li>'; }).join('') + '</ul>');
},
text: function(){
res.send(users.map(function(user){ return ' - ' + user.name + '\\n'; }).join(''));},
json: function(){ res.json(users); }
})
});And it works. It’s deliberate. The intent is clear. My problem is that the content negotiation code happens in the routing logic. I don’t want it there. It’s not following the Single Responsibility Principle. The routing logic is supposed to get a list of users. Not get a list of users AND format it. So while I have the flexibility to change the resource for each representation, I have to modify routing code to do it. The view has not been encapsulated away as something that varies often. So my gut is telling me not to do this.
What I really want is the content negotiation to happen elsewhere. What I really want in my routing logic is to just respond with a list of users. And I want the code that determines the representation down stream of the request pipeline. How ‘bout I have code like:
app.get('/', function(req, res, next){ res.send(users);});That would be awesome! But I haven’t been able to get it to just that; I got close though. Here’s what my routing logic looks like:
app.get('/', function(req, res, next){ res.send({resource: self, view: "users/index", model:{header: "Purpose", copy: "Use a representational paradigm with ExpressJS."}})I overwrote the Express JS’s response.send method which I then wrote code to negotiate content, pick the view with the negotiated content’s file extension and render that.
The code base is made up of resources and views. There’s a resources folder that gets loaded upon application boot which loads all the routing code. Each resource’s routing code calls response.send with a view folder and file (without the file extension), a resource and model object. The view folder contains a layouts folder that contains the layout files for each representation (default.html, default.xml, etc.). The represent method determines the file extension to use when looking up a layout and view, via the file extension specified (or not) in the URL path, finds the layout and view and renders it, responding to the client with the output, in addition to setting the correct content type.
I hope this idea rings true to you. I’d love critical feedback. I want to take this idea and make it better.
Thanks for reading.
