Recently I built and shipped a fun project called Nomad Icon (nmdi.co). It is an embeddable icon that shows your current location and changes color dynamically depending on the length of the stay.

I wanted to hack something with plain old node.js without flashy features that come with full-stack frameworks such as Meteor. Built with Express, this project is a result of my desire to hack.

In this post, I will share the story of making Nomad Icon. By the way, it is a free software and the source code is hosted on the GitHub repo.

Overview

Status badges are popular on projects hosted on GitHub. There seem to be badges for everything: build status, test coverage, code quality, number of people in a chat room, etc.

Some time ago, I had an idea that it will be fun to build a badge for nomads, but I did not know where to start. I recently discovered an open source project called nodei.co which is an icon for node modules. It gave me some ideas of how to start building such icon for nomads.

NPM

To cut to the chase, this is what my badge looks like now:

If I had moved to Sydney fairly recently, the color of the badge would be different:

Getting there

From node-canvas to svg

First, I set out to draw a badge in a png format using node-canvas project, which allows you to use HTML5 canvas API on the server side with node.js. But the workflow was inefficient.

To draw something simple, I had to maneuver complex Canvas APIs and refresh the page to see if the change is what I had pictured in my head.

For instance, here is what I had to do to draw the outline of the badge:

function drawOutline(ctx) {
  ctx.fillStyle = '#E6E6E6';
  ctx.fillRect(10, 10, 280, 85);

  ctx.lineWidth = 2;
  ctx.strokeStyle = 'rgba(161, 192, 234, 0.8)';
  ctx.strokeRect(10, 10, 280, 85);

  ctx.beginPath();
  ctx.moveTo(10, 35);
  ctx.lineTo(290, 35);
  ctx.stroke();

  ctx.beginPath();
  ctx.moveTo(200, 10);
  ctx.lineTo(200, 35);
  ctx.stroke();
}

Not exactly a concise solution for drawing a simple rectangle with a line-through.

Also, as you start drawing stuff inside the rectangle, it becomes very difficult to fine-tune all the values. If you change one value you need to remember to change other values that depend on that. For instance, the length of a bisecting line depends on the width of the rectangle.

So I decided to generate svg rather than png. That way, I do not have to use Canvas API, but only need to write SVG markup. Here is how I am drawing the outline with svg.

<rect x="0" y="0"
      rx="3" ry="3"
      width="80" height="20"
      fill="#332532" />
<rect x="<%= textStart %>"
      rx="3" ry="3"
      width="<%= textWidth + 2 * margin %>" height="20"
      fill="<%= iconColor %>" />

To mitigate the complex dependencies between values, I abstracted the variable calculation into a module, and used ejs to dynamically pass those values to the svg template. As a result, svg template is first rendered by ejs and then served to the client.

Also, I made the icon as minimalistic as possible, and display only the location. My original plan was to make something similar to nodei.co, displaying a bunch of information in a moderately sized icon; but I felt people would be more inclined to embed a simple one-line icon than a complex one.

Bootstrapping

The airplane that appear in the icon, favicon, and the Twitter app picture is the plain old FontAwesome icon.

To use it in the svg, I had to use this hack. And I captured the screen and generated favicon and app icon with Favicon Generator.

Challenges

Secret environment variables

To avoid committing secret keys, I set out to use environment variable in the server. But I was going to host the app in the server that is already hosting a number of my pet projects. To avoid variable name collisions, I wanted to containerize the app and set environment variable only within that container.

This seemed to be a good opportunity to give Docker a try, but after some reading, I found that a container’s environment variables are not kept secret.

Rather than busting my head with a limited Docker knowledge, I just namespaced the variables with NMDICO_ and set them in my server using export command.

Dynamic icon width

The icon has to change width dynamically depending on the length of the string it is displaying. But calculating width is tricky because there is no dimension on the server side.

My initial attempt to solve the problem was to create a jQuery element, fill it in with the text, and measure width.

var $ = require('jquery')(require("jsdom").jsdom().parentWindow);

function calcWidth(text) {
  var span = $('<span />').html(text);
  return span.width();
}

But this solution always returned 0 there is no DOM in the server.

After some research, I found that HTML5 canvas has mesaureText() method. A light bulb flashed in my head and I implemented a solution using node-canvas.

var Canvas = require('canvas');

function calcWidth(text, fontSize, fontFamily) {
  var canvas = new Canvas(500, 50)
    , ctx = canvas.getContext('2d');

  ctx.font = fontSize + 'px ' + fontFamily;
  return ctx.measureText(text).width;
}

A little drawback is that I have to install some dependencies on my Ubuntu server to use node-canvas, just for these few lines of code. But this logic is important to generate icons, so I went with this solution.

Getting the words out

I posted on Hacker News and digitalnomad subreddit. Some people liked it on Reddit.

And 25 People tried it.

I think Hacker News and Reddit are the easiest way to bring personal projects to the world or to a specific niche. Just a few clicks and your project is out there. But some subreddits’ spam filters are pretty aggressive and can give you a hard time.

Architecture

Right now, the architecture is a bare minimum. There is a single instance of the Express application and a single MongoDB process running in a server running Ubuntu and nginx.

Just as a thought experiment, these are the architecture that I think will be efficient at scale.

Candiate 1

Each server will run four node.js apps to utilize quad core CPUs, and I will place a load balancer to distribute traffics in a round-robin fashion. All the apps will talk to the same MongoDB replica set.

Candidate 2

Maybe I could horizontally scale a portion of the app in a microservice style. There could be API servers responsible for generating and serving icons, and a front-end app that handles general user interaction.

Closing

It was a good opportunity to brush up my knowledge of node.js and Express framework in general. It would have taken shorter to ship the working version, had I used Meteor. But this project did not really need reactive updates or latency compensation. I wanted to keep it simple.

Sometimes all you need to get started is a small push toward a right direction. When I did not know where to start to build this project, nodei.co project illuminated the path for me. By open sourcing this project, I hope to inspire someone in the future who wants to hack something similar. And I wish this helps them grow.