Following on from my recent post about doing something this year, I’m committing to doing 12 months of “101”s; posts and projects themed at begining something new (or reasonably new) to me
January is all about node, and I started with a basic intro, then cracked open a basic web server with content-type manipulation and basic routing, created a basic API, before getting stuck into some great deployment and hosting solutions
Node Packages
Up until now I’ve been working with node using the basic code I’ve written myself. What about if you want to create an application that utilises websockets? Or how about a Sinatra-inspired web framework to shortcut the routing and request handling I’ve been writing? Maybe you want to have a really easy to build website without having to write HTML with a nice look without writing any CSS? Like coffeescript? mocha? You gaddit.
Thanks to the node package manager you can easily import pre-built packages into your project to do alllll of these things and loads more. This command line tool (which used to be separate but is now a part of the node install itself) can install the packages in a ruby gem-esque/.Net nuget fashion, pulling down all the dependencies automatically.
Example usage:
npm install express -g
The packages (compiled C++ binaries, just like node itself) are pulled either into your working directory (local node_modules folder) or as a global package (with the “-g” parameter). You then reference the packages in your code using “requires”.
Or you can install everything your project needs at once by creating a package.json e.g.:
{ "name": "basic-node-package", "version": "0.0.1", "dependencies": { "express": "*", "jade": "*", "stylus": "*", "nib": "*" } }
And then call
npm install
A great intro to using these four packages can be found on the clock website
I’ve decided to write a wrapper for my basic node API using express, jade, stylus, and nib. All I’m doing is call the api and displaying the results on a basic page. The HTML is being written in jade and the css in stylus & nib. Routing is being handled by express.
app.js
var express = require('express') , stylus = require('stylus') , nib = require('nib') , proxy = require('./proxy') var app = express() function compile(str, path) { return stylus(str) .set('filename', path) .use(nib()) } app.set('views', __dirname + '/views') app.set('view engine', 'jade') app.use(express.logger('dev')) app.use(stylus.middleware( { src: __dirname + '/public' , compile: compile } )) app.use(express.static(__dirname + '/public')) var host = 'rposbo-basic-node-api.azurewebsites.net'; app.get('/products/:search/:key', function (req,response) { console.log("Request handler 'products' was called"); var requestPath = '/products/' + req.params.search + '?key=' + req.params.key; proxy.getRemoteData(host, requestPath, function(json){ var data = JSON.parse(json); response.render('products', { title: 'Products for' + data.category, products: data.products, key: req.params.key } ); }) }); app.get('/product/:id/:key', function (req,response) { console.log("Request handler 'product' was called"); var requestPath = '/product/' + req.params.id + '?key=' + req.params.key; proxy.getRemoteData(host, requestPath, function(json){ var data = JSON.parse(json); response.render('product', { title: data.title, product: data } ); }) }); app.get('/', function (req,response) { console.log("Request handler 'index' was called"); response.end("Go"); }); app.listen(process.env.PORT);
So that file sets up the express, jade, and stylus references and wires up the routes for /products/ and /product/ which then make a call using my old proxy.js to the API; I can probably do all of this with a basic inline http get, but I’m just reusing it for the time being.
Notice how the route “/products/:search/:key” which would actually be something like “/products/jeans/myAp1k3Y” is referenced using req.params.search and req.params.key.
Then all I’m doing is making the API call, parsing the returned JSON and passing that parsed object to the view.
The views are written in jade and have a main shared one:
layout.jade
!!!5 html head title #{title} link(rel='stylesheet', href='/stylesheets/style.css') body header h1 basic-node-packages .container .main-content block content .sidebar block sidebar footer p Running on node with Express, Jade and Stylus
Then the route-specific ones:
products.jade:
extend layout block content p each product in products li a(href='/product/' + product.id + '/' + key) img(src=product.image) p =product.title
and
product.jade:
extend layout block content p img(src=product.image) li= product.title li= product.price
The stylesheet is written in stylus & nib:
style.styl
/* * Import nib */ @import 'nib' /* * Grab a custom font from Google */ @import url('http://fonts.googleapis.com/css?family=Quicksand') /* * Nib provides a CSS reset */ global-reset() /* * Store the main color and * background color as variables */ main-color = #fa5b4d background-color = #faf9f0 body font-family 'Georgia' background-color background-color color #444 header font-family 'Quicksand' padding 50px 10px color #fff font-size 25px text-align center /* * Note the use of the `main-color` * variable and the `darken` function */ background-color main-color border-bottom 1px solid darken(main-color, 30%) text-shadow 0px -1px 0px darken(main-color, 30%) .container margin 50px auto overflow hidden .main-content float left p margin-bottom 20px li width:290 float:left p line-height 1.8 footer margin 50px auto border-top 1px dotted #ccc padding-top 5px font-size 13px
And this is compiled into browser-agnostic css upon compilation of the app.
The other files used:
proxy.js:
var http = require('http'); function getRemoteData(host, requestPath, callback){ var options = { host: host, port: 80, path: requestPath }; var buffer = ''; var request = http.get(options, function(result){ result.setEncoding('utf8'); result.on('data', function(chunk){ buffer += chunk; }); result.on('end', function(){ callback(buffer); }); }); request.on('error', function(e){console.log('error from proxy call: ' + e.message)}); request.end(); }; exports.getRemoteData = getRemoteData;
package.json
{ "name": "basic-node-package", "version": "0.0.1", "dependencies": { "express": "*", "jade": "*", "stylus": "*", "nib": "*" } }
web.config
<configuration> <system.web> <compilation batch="false" /> </system.web> <system.webServer> <handlers> <add name="iisnode" path="app.js" verb="*" modules="iisnode" /> </handlers> <iisnode loggingEnabled="false" /> <rewrite> <rules> <rule name="myapp"> <match url="/*" /> <action type="Rewrite" url="app.js" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
All of these files are, as usual, on Github
Deployment with Packages
Something worth bearing in mind is that deploying something which includes packages and the result of packages (e.g. minified js or css from styl) requires all of these artifacts to be added into your git repo before deployment to certain hosts such as Appharbor and Azure; Heroku will actually run an npm install as part of the deployment step, I believe, and also compile the .styl into .css, unlike Azure/Appharbor.
The files above give a very basic web interface to the /products/ and /product/ routes:
Coming up
Web-based node development and deployment!
One thought on “Node.js 101: Part #5 – Packages”