A List Apart

Menu
Issue № 374

Node at Work: A Walkthrough

by Published in JavaScript · 19 Comments

In my first article, “Even Better In-Browser Mockups with Node.js,” I explained why Node.js makes designing applications easier and more efficient, and how to get started. Now it’s time to see your new design process in action.

Rather than figuring out all your requirements and API schemas just to design your comps with mockup content hard-coded and server interactions faked—only to throw it all away when you go back and implement things “for real”—you can use Node.js to skip the hard-coding and produce client-side code that’s ready for beta at the end of the design stage.

The process looks a lot like good ol’ designing in the browser, but with more JavaScript and an additional layer:

  1. Design the layout and styling
  2. Convert the markup to a JavaScript template
  3. Create an initialization function
  4. Create a simple Node.js server
  5. Add a mockup data object to the server
  6. Add server functions to serve static pages and JSON
  7. Request and consume the JSON on the client

Sound daunting? Don’t worry. The first step takes approximately a zillion times longer than any of the others. So if you’ve already mastered the design, you’ll find the rest of these steps more than manageable.

In this walkthrough, we’ll build a feature for a mock art store. If you want to follow along at home, you can clone my GitHub repository. (If you need help installing, see the README, or just take a peek at the live demo—I’ll cover all the steps and code below.)

Creating templates

Once you have a solid design and the markup to accompany it, converting it to a template you can use for all examples is more efficient than creating duplicate markup for each one. The hard part’s over; you already thought about where data points would be used in the design when you created it. With those choices fresh in your mind, go back and mark up your HTML with data in whatever template language you prefer.

For my example, I’m using a store selling art prints. Here’s a snippet of my initial markup:

<h2>Two Acrobats with a Dog</h2>
<h3>Pablo Picasso</h3>
<img src="img/102.jpg" alt="Two Acrobats with a Dog" class="active" />
<ul class="info">
	<li>8" x 11"</li>
	<li>acid-free paper</li>
	<li>suitable for matting</li>
</ul>
<span class="price">$49.99</span>

Think of your templates as places to define your requirements for both data and its formatting on the client side. If you can also reuse it for client-side rendering, that’s awesome—but that may not be relevant to your application. As long as you have good data, converting from one template language to another is trivial, so don’t agonize over which template engine to use.

You do need a template engine that will work in both the browser and Node.js, however. If you’re unsure, search for your template engine on GitHub and verify that there’s a guide to installing it via npm in the manual, as well as a minified script for use on the client. I prefer doT.js, so here’s that snippet again marked up to add data using doT:

<h2>{{=it.title}}</h2>
<h3>{{=it.artist.name}}</h3>
<img src="img/{{=it.id}}.jpg" alt="{{=it.title}}" class="active" />
<ul class="info">
	{{~it.info :info_item}}
	<li>{{=info_item}}</li>
	{{~}}
</ul>
<span class="price">{{=it.price}}</span>

I like to save my templates in their own directory at the same level as my JavaScript directory, so now I store that as tmpl/detail.dot.

Initializing the client

Since we want to be able to use our templates in both Node and the browser, they need to be stored outside of the HTML and loaded and compiled when we open the page. To start, save the minified version of your template engine and add a script tag to your page to include it. Once that’s done, you can fetch the template, compile it, and then continue on with any other initialization work in your main JavaScript file. I’m using jQuery in my example, so my code looks like this:

var detailTmpl;

$.when( 
	$.get( "tmpl/detail.dot", function( tmpl ) {
		detailTmpl = doT.template( tmpl );
	}, "text" ) 
).then( init );

That mysterious init function? That’s where I’ll put any interactivity I want to add to my currently static mockup. For the moment I’m only creating one interaction, so my init function is pretty simple:

function init() {
	$( "div.content" ).on( "click", "div.result", showDetail );
}

This code can be made much more elegant using Require.js with its text plugin. That’s beyond the scope of this demo, but I highly encourage it for production.

We’ll handle template rendering in showDetail(), but we have to add a server and data store before writing that function, since right now we lack any data to render.

Creating a server

If I reload my page now and open the browser console, I get a JavaScript error. That’s because I’m trying to load my template via an XMLHttpRequest (XHR) on a page being served from the file system, in violation of the same origin policy. I can’t even check that my template works until the page is served properly (i.e., from a server).

To whip up a simple Node server that allows me to run my XHRs, I do a few things:

  • Move all my existing assets into a new subdirectory called public
  • Open my terminal or command line to my working directory and run npm install express
  • Add a server.js file to the working directory

We could write everything from scratch, of course, but it’s more work than is necessary for a basic server. The Express framework provides a number of abstractions of server and application concepts. For the initial version of the server, the only one we’ll need is its ability to serve static resources. We can use it by adding four lines of code to server.js:

var express = require( "express" ),
	app = express();

app.use( express.static( __dirname + "/public" ) );

app.listen( 3000 );

Once you start your server by typing node server.js in your open terminal or command line, you can view your page at http://localhost:3000 (adding a filename if necessary), and the error related to loading the template ought to disappear.

Adding server-side data

While it’s certainly nice to be able to use XHRs, we’re creating the Node server to use it as a representation of the real server—and real servers store data. Though it’s not hard to create a data store that works with a Node server, it’s even less difficult to create one big object literal. For a mockup, that’s all we really need. One of the goals here is to define the data objects we need to support in our new design, so the format of this object can be determined by the template we just added. For my example, I need an object structured something like this:

var products = {
	"102": {
		id: 102,
		title: "Two Acrobats with a Dog",
		artist: {
			name: "Pablo Picasso"
		},
		price: "$49.99",
		info: [
			"8\" x 11\"",
			"acid-free paper",
			"suitable for matting"
		]
	}
};

Note that products could just as easily be an array, but I want to be able to quickly find my products—once I have more than one in my fake data store—by ID. Aside from that little twist, the data look exactly like the content hard-coded in my original HTML. If I want to add more data, including things that might break the layout in unpredictable ways, I can just copy this structure and make substitutions. Well, almost.

Returning data from the server

If you’ve dealt with other server-side frameworks, creating endpoints for XHRs might seem intimidating, but Express makes it really easy. We don’t need any special setup to define a server endpoint as a target for asynchronous requests. All we have to do is define the path on the server where we want to accept requests and a callback. The callback receives a request object (for doing things like getting passed-in data) and a response object (for defining what we return to the client). To return the data in my products object, I add a few lines of code at the bottom of server.js:

app.get( "/detail/:id", function( req, res ) {
	res.send( products[ req.params.id ] );
});

app.listen( 3000 );

See? Easy. If I restart my server and go to http://localhost:3000/detail/102, I should see my object data. To break down what’s going on with the ID in the path, we’ve named the data at that position in the path "id" with the :id bit, and it then becomes available as a property of req.params.

The names and positions of parameters are up to us, and if our path were super complex, we could also use regular expressions to split out multiple pieces of data. Express also gives us the option of accepting data from the query string or from a POST. Of all the pieces we’re creating, however, the paths are the most likely to change in production, so it’s to our advantage to keep them as readable as possible.

Besides sending pure data to the client, we also want to be able to send rendered HTML, in case a user is linked directly to a product detail or doesn’t have JavaScript available. We might also want HTML for our own consumption via XHR, if we find that client-side rendering is slowing us down. So we add a second endpoint below the one we just created to do that:

app.get( "/product/:id", function( req, res ) {
	res.render( "detail", products[ req.params.id ] );
});

For simplicity’s sake, and because the first path served JSON for an overlay while this provides a full page, I’ve used a different pathname, but kept the same pattern. This time, instead of the response’s send function, I use render(). Express provides some magic to make template rendering work out of the box, but since I’m using doT instead of Jade (the default template engine of Express), I have to do some additional setup.

First I have to go back to the terminal or command line, stop my Node server, and install my template engine using npm install doT and the consolidate module (which provides Express compatibility for a number of popular template engines) using npm install consolidate. Now I’ve got both of those in my node_modules directory and can use them in server.js.

Since doT (and probably your template engine of choice, as well) is accessed through consolidate, consolidate is the only additional module I need to require at the top of server.js:

var express = require( "express" ),
	app = express(),
	cons = require( "consolidate" );

I want to continue serving some of my other pages statically, so I add my template configuration stuff below the existing app.use line in my code:

app.use( express.static( _dirname + "/public" ) );
app.engine( "dot", cons.dot );
app.set( "view engine", "dot" );
app.set( "views", _dirname + "/public/tmpl" );

Those three new lines set doT (as exposed by consolidate) as the view engine, register files ending in .dot as templates, and tell Express to look in /public/tmpl for templates to use. So when Node sees res.render( "detail", { ... } ), it knows to expand "detail" to /public/tmpl/detail.dot and render it as a doT template. Now I can restart my server, go to http://localhost:3000/product/102, and see my template rendered statically, without creating a separate server-side file.

Fetching dynamic data

Our template now works as a static page, but there’s still one more step to get our mockup populated with the data from the server. Remember the showDetail function from our main client-side script? It’s time to flesh that out.

In my simple example, the overlay my template will populate already exists as a hidden div on the main page, and it appears when the user clicks a div containing a summary of the content. This div has a data attribute storing the ID of the product that corresponds to the key and id property in my server-side data object. Once that click event happens and showDetail() is called, I just need to do this:

function showDetail( e ) {
	var id = $( this ).data( "id" );
	$.get( "detail/" + id, function( info ) {
		$( "div.detail" ).html( detailTmpl( info ) );
		$( "div.detail" ).show();
	}
}

The path above is the same one I defined in server.js. If you chose a different name for yours, use that name here on the client. When I receive the data object from the server, I pass it to detailTmpl(), the compiled version of my template. The result of the detailTmpl function is the HTML to populate my overlay.

Onward

So there you have it! A mockup that mimics the interactions it will have with its production server precisely on the client, without the need for hard-coded data or temporary workarounds. Despite the simple exercise, the process I’ve outlined accomplishes a good deal of the setup necessary to create other workflows that require server interactions. For instance, I can fill my fake data store with more products and use that to render the initial page that triggers my overlay without having to revisit my mockup data, and my application will show the correct values in any view I add to it.

If you’d like to explore beyond just serving HTML and JSON, consider adding in Socket.io to allow real-time interaction for multiple clients or Require.js to manage your assets on the client. You could also move your CSS into templates and serve different builds of your site for different browsers or devices. Your mockup can be as sophisticated and reflect as many of its production requirements as you choose. At the end, the lion’s share of your client-side code is done and ready to use.

About the Author

19 Reader Comments

Load Comments