- javascript 2017-03-19T16:58:15+01:00 https://fadeit.dk/blog/tag/javascript.html Loading translations from Google Spreadsheets 2015-09-04T11:16:30+02:00 https://fadeit.dk/blog /2015/09/04/managing-translations-with-gulp-or-grunt <h2 id="introduction">Introduction</h2> <p>This is a follow-up post to our <a href="https://fadeit.dk/blog/post/managing-angular-translate-translations">previous</a> translation management article. To recap - in that article we solved the issue of loading ‘fresh’ translation on development server using nginx and proxying translation requests to the api which in turn will fetch new translations from Google Spreadsheets. It can be seen as overkill - this flow complicates nginx configuration, slows down translation loading on every refresh and will make a ton of (unnecessary) requests while developing. While it’s nice to always get the latest translations, truth is that most of the time you don’t need to fetch translations from the cloud. In this post we’ll explore loading translations only during the build process, thus eliminating the need to proxy requests and write api endpoint in the first place.</p> <h2 id="workflow">Workflow</h2> <p>Since writing previous post, I have also changed my mind about lazy-loading translations using partial loader. This is primarily due to the lengths I had to go to cloak translations while they’re loading, especially considering that the size of the translations file is marginal for most applications. By packaging translations in the same minified file as the application I don’t have to worry about <a href="https://fadeit.dk/blog/post/preload-angular-translate-partial-loader">pre-loading</a> translations or flickering as they are being loaded. So we will introduce an extra task to our workflow that will fetch translations from Google Spreadsheets and converts it to a javascript file ready to be used by angular-translate.</p> <h2 id="using-gulp">Using Gulp</h2> <p>Although here at fadeit we started out with using Grunt exclusively, we’ve come to appreciate Gulp more in our recent projects. Primarily since writing custom tasks in good ol’ JavaScript is more intuitive as opposed to Grunt’s configuration approach. Surely piping the result of previous task directly to next one without having to write it to disk is a plus, but not in current context.</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'gulp'</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'request'</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'fs'</span><span class="p">);</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="s1">'translations'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">cb</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">uuid</span> <span class="o">=</span> <span class="s1">'1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk'</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">page</span> <span class="o">=</span> <span class="s1">'od6'</span> <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="s1">'https://spreadsheets.google.com/feeds/list/'</span> <span class="o">+</span> <span class="nx">uuid</span> <span class="o">+</span> <span class="s1">'/'</span> <span class="o">+</span> <span class="nx">page</span> <span class="o">+</span> <span class="s1">'/public/values?alt=json'</span><span class="p">;</span> <span class="nx">request</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">body</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">json</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">body</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">translations</span> <span class="o">=</span> <span class="p">{</span> <span class="s1">'en'</span><span class="p">:</span> <span class="p">{},</span> <span class="s1">'da'</span><span class="p">:</span> <span class="p">{}</span> <span class="p">};</span> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">json</span><span class="p">.</span><span class="nx">feed</span><span class="p">.</span><span class="nx">entry</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">entry</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">feed</span><span class="p">.</span><span class="nx">entry</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span> <span class="kd">var</span> <span class="nx">key</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">gsx$key</span><span class="p">.</span><span class="nx">$t</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">enValue</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">gsx$en</span><span class="p">.</span><span class="nx">$t</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">daValue</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">gsx$da</span><span class="p">.</span><span class="nx">$t</span><span class="p">;</span> <span class="nx">translations</span><span class="p">.</span><span class="nx">en</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">enValue</span><span class="p">;</span> <span class="nx">translations</span><span class="p">.</span><span class="nx">da</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">daValue</span><span class="p">;</span> <span class="p">}</span> <span class="nx">writeTranslations</span><span class="p">(</span><span class="nx">translations</span><span class="p">,</span> <span class="nx">options</span><span class="p">.</span><span class="nx">src</span> <span class="o">+</span> <span class="s1">'/app/translations.js'</span><span class="p">,</span> <span class="nx">cb</span><span class="p">);</span> <span class="p">});</span> <span class="p">});</span> <span class="kd">function</span> <span class="nx">writeTranslations</span><span class="p">(</span><span class="nx">translations</span><span class="p">,</span> <span class="nx">file</span><span class="p">,</span> <span class="nx">cb</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">contents</span> <span class="o">=</span> <span class="s2">"var appModule = angular.module('fadeit');\n"</span><span class="p">;</span> <span class="nx">contents</span> <span class="o">+=</span> <span class="s2">"appModule.config(function($translateProvider){\n"</span><span class="p">;</span> <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">property</span> <span class="k">in</span> <span class="nx">translations</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">lang</span> <span class="o">=</span> <span class="nx">translations</span><span class="p">[</span><span class="nx">property</span><span class="p">];</span> <span class="nx">contents</span> <span class="o">+=</span> <span class="s2">"$translateProvider.translations('"</span> <span class="o">+</span> <span class="nx">property</span> <span class="o">+</span> <span class="s2">"',\n"</span><span class="p">;</span> <span class="nx">contents</span> <span class="o">+=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">lang</span><span class="p">);</span> <span class="nx">contents</span> <span class="o">+=</span> <span class="s2">");\n"</span><span class="p">;</span> <span class="p">}</span> <span class="nx">contents</span> <span class="o">+=</span> <span class="s2">"});"</span><span class="p">;</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="nx">contents</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">){</span> <span class="k">if</span><span class="p">(</span><span class="nx">err</span><span class="p">){</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">'Writing translations failed:'</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">}</span> <span class="nx">cb</span><span class="p">();</span> <span class="p">});</span> <span class="p">}</span> </code></pre> </div> <p>As seen from the code, couple modules are used - namely <code class="highlighter-rouge">request</code> to fetch the JSON data and <code class="highlighter-rouge">fs</code> to write a file. The task won’t only write the file, but it will also generate an angular.js module file that will configure translations ready to be used. It can be seen a bit hacky to generate javascript file like that, but I’ve yet to come up with more clean solution. The upside is that this code is not prone to change and I haven’t touched it since writing it. There’s caveat though - if you are using <a href="http://www.browsersync.io/">browsersync</a>, then it may trigger refresh before the translations are actually pulled (because other files changed before). To fix this, we need to make sure that translations are loaded before any other tasks take place. It is easily solved with gulp-sync module, to execute certain tasks synchronously. Same would also apply if the process will concatenate files together - we need to make sure new translations are loaded before that happens. In the sample build process below I’ve split up the build process in two portions where each of them is executed asynchronously. The callback at the end of the gulp task is necessary so that gulp-sync knows when to start processing next batch of tasks.</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">gulpsync</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'gulp-sync'</span><span class="p">)(</span><span class="nx">gulp</span><span class="p">);</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="s1">'build'</span><span class="p">,</span> <span class="nx">gulpsync</span><span class="p">.</span><span class="nx">sync</span><span class="p">([[</span><span class="s1">'translations'</span><span class="p">,</span> <span class="s1">'clean'</span><span class="p">],</span> <span class="p">[</span><span class="s1">'html'</span><span class="p">,</span> <span class="s1">'scripts'</span><span class="p">]]));</span> </code></pre> </div> <h2 id="using-grunt">Using Grunt</h2> <p>Firstly I must apologise as I did not take time to develop a pure grunt way of doing so. This is because it started out as a python script which I later incorporated in the grunt workflow. The script will serve same purpose as the one above, with a minor difference of calling an external python executeable instead.</p> <div class="language-python highlighter-rouge"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span> <span class="kn">import</span> <span class="nn">urllib.request</span> <span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">OrderedDict</span> <span class="n">sheet_id</span> <span class="o">=</span> <span class="s">'1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk'</span><span class="p">;</span> <span class="n">page_id</span> <span class="o">=</span> <span class="s">'od6'</span> <span class="n">base</span> <span class="o">=</span> <span class="s">'https://spreadsheets.google.com/feeds/list/{0}/{1}/public/values?alt=json'</span> <span class="n">translation_config</span> <span class="o">=</span> <span class="s">'src/app/translations.js'</span> <span class="n">url</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sheet_id</span><span class="p">,</span> <span class="n">page_id</span><span class="p">)</span> <span class="n">json_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf8'</span><span class="p">))</span> <span class="c">#we need to build different data structure</span> <span class="n">en_translation_map</span> <span class="o">=</span> <span class="n">OrderedDict</span><span class="p">()</span> <span class="n">da_translation_map</span> <span class="o">=</span> <span class="n">OrderedDict</span><span class="p">()</span> <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">json_data</span><span class="p">[</span><span class="s">'feed'</span><span class="p">][</span><span class="s">'entry'</span><span class="p">]:</span> <span class="n">key</span> <span class="o">=</span> <span class="n">entry</span><span class="p">[</span><span class="s">'gsx$key'</span><span class="p">][</span><span class="s">'$t'</span><span class="p">]</span> <span class="n">en_value</span> <span class="o">=</span> <span class="n">entry</span><span class="p">[</span><span class="s">'gsx$en'</span><span class="p">][</span><span class="s">'$t'</span><span class="p">]</span> <span class="n">da_value</span> <span class="o">=</span> <span class="n">entry</span><span class="p">[</span><span class="s">'gsx$da'</span><span class="p">][</span><span class="s">'$t'</span><span class="p">]</span> <span class="n">en_translation_map</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">en_value</span> <span class="n">da_translation_map</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">da_value</span> <span class="c">#Javascript for translations file we are generating</span> <span class="n">config</span> <span class="o">=</span> <span class="s">""" var appModule = angular.module('fadeit'); appModule.config(function($translateProvider){ """</span> <span class="n">en_translations</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">en_translation_map</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="n">da_translations</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">da_translation_map</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">$translateProvider.translations('en',"</span> <span class="o">+</span> <span class="n">en_translations</span> <span class="o">+</span> <span class="s">');'</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">$translateProvider.translations('da',"</span> <span class="o">+</span> <span class="n">da_translations</span> <span class="o">+</span> <span class="s">');'</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">});"</span> <span class="c">#close function</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">translation_config</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">outfile</span><span class="p">:</span> <span class="n">outfile</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">config</span><span class="p">)</span> </code></pre> </div> <p>Now we can save this file in the project (in our case src/translations.py) and call it using grunt-shell during build time.</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">shell</span><span class="err">:</span> <span class="p">{</span> <span class="nl">translations</span><span class="p">:</span> <span class="p">{</span> <span class="nl">options</span><span class="p">:</span> <span class="p">{</span> <span class="nl">stdout</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">command</span><span class="err">:</span> <span class="s1">'python3 src/translations.py'</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Since grunt executes tasks synchronously, all there is left to do is call this task in the beginning of the build process and it’ll generate a following file.</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">appModule</span> <span class="o">=</span> <span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s1">'fadeit'</span><span class="p">);</span> <span class="nx">appModule</span><span class="p">.</span><span class="nx">config</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">$translateProvider</span><span class="p">)</span> <span class="p">{</span> <span class="nx">$translateProvider</span><span class="p">.</span><span class="nx">translations</span><span class="p">(</span><span class="s1">'en'</span><span class="p">,</span> <span class="p">{</span> <span class="s2">"HELLO"</span><span class="p">:</span> <span class="s2">"Hello"</span> <span class="p">}</span> <span class="p">);</span> <span class="nx">$translateProvider</span><span class="p">.</span><span class="nx">translations</span><span class="p">(</span><span class="s1">'da'</span><span class="p">,</span> <span class="p">{</span> <span class="s2">"HELLO"</span><span class="p">:</span> <span class="s2">"Hej"</span> <span class="p">}</span> <span class="p">);</span> <span class="p">});</span> </code></pre> </div> <h2 id="closing-words">Closing words</h2> <p>Over-all I’ve come to prefer loading translations during build time instead. While it will add few seconds to the build process, I will make it up by not having to wait for translations being proxied on every reload. If I already have a server running and just wish to reload translations, I can issue the command directly instead of re-building everything. For gulp it would be <code class="highlighter-rouge">gulp translations</code> and for grunt <code class="highlighter-rouge">grunt shell:translations</code>. I’ve also decided to ditch versioning translations and generally will add <code class="highlighter-rouge">src/translations.js</code> to <code class="highlighter-rouge">.gitignore</code>.</p> Testing Express.js + CouchDB Applications with mock-couch 2015-07-05T22:30:00+02:00 https://fadeit.dk/blog /2015/07/05/testing-express-couchdb-apps-with-mock-couch <h2 id="prerequisites">Prerequisites</h2> <p>The code in this post is based upon <a href="http://coffeescript.org/">CoffeeScript</a>, <a href="http://mochajs.org/">Mocha</a> and <a href="http://gulpjs.com/">Gulp</a> but it should be easily adaptable to other stacks (e.g. <a href="https://www.javascript.com/">JavaScript</a>, <a href="https://jasmine.github.io/">Jasmine</a> and <a href="http://gruntjs.com/">Grunt</a>).</p> <h2 id="intro">Intro</h2> <p>Recently, I worked on a <a href="http://expressjs.com/">Express.js</a> based RESTful API with <a href="https://couchdb.apache.org/">CouchDB</a> storage. For various reasons, I mostly had end-to-end tests (testing HTTP endpoints) and very few unit tests. In the tests, I would rebuild the database before every <code class="highlighter-rouge">describe</code> block. This worked quite well but at one point I noticed that my feedback loops were beginning to get tediously slow (~30 seconds to run the full test suite). Thankfully, I discovered <a href="https://github.com/chris-l/mock-couch">mock-couch</a>: in-memory storage with CouchDB API. The switch was rather easy and, as a result, my test suite would finish in less than 10 seconds (even when reseting test data before each test).</p> <h2 id="setup">Setup</h2> <p>Our sample API will expose functionality to access a database of sofa’s. Let’s set it up.</p> <p>Structure and dependencies</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>mkdir -p sofa-api/specs <span class="nb">cd </span>sofa-api npm install mock-couch coffee-script gulp gulp-mocha should supertest express nano q </code></pre> </div> <p>gulpfile.coffee</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">require</span> <span class="s">'coffee-script/register'</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'gulp'</span> <span class="nx">mocha</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'gulp-mocha'</span> <span class="nx">sources</span> <span class="o">=</span> <span class="na">tests</span><span class="o">:</span> <span class="s">'./specs/**/*.spec.coffee'</span> <span class="c1"># Task to run tests with mock-couch backend</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'test'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'test'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3010</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5987</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas_test'</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">src</span><span class="p">(</span><span class="nx">sources</span><span class="p">.</span><span class="na">tests</span><span class="p">)</span> <span class="p">.</span><span class="na">pipe</span><span class="p">(</span><span class="nx">mocha</span><span class="p">())</span> <span class="c1"># Task to run integration tests (real CouchDB backend)</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'test-int'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'test-int'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3010</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5984</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas_test'</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">src</span><span class="p">(</span><span class="nx">sources</span><span class="p">.</span><span class="na">tests</span><span class="p">)</span> <span class="p">.</span><span class="na">pipe</span><span class="p">(</span><span class="nx">mocha</span><span class="p">())</span> <span class="c1"># Task to run our server</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'default'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'development'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3000</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5984</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas'</span> <span class="nx">server</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'./server'</span> <span class="nx">server</span><span class="p">.</span><span class="na">listen</span><span class="p">()</span> </code></pre> </div> <p>server.coffee</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'express'</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">()</span> <span class="nx">app</span><span class="p">.</span><span class="na">get</span> <span class="s">'/sofas'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="s">'not implemented'</span> <span class="nx">app</span><span class="p">.</span><span class="na">get</span> <span class="s">'/sofas/:sofa'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="s">'not implemented'</span> <span class="nx">exports</span><span class="p">.</span><span class="na">listen</span> <span class="o">=</span> <span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="nx">app</span><span class="p">.</span><span class="na">listen</span> <span class="nx">port</span><span class="p">,</span> <span class="nx">callback</span> </code></pre> </div> <p>Let’s create file <em>specs/sofas.spec.coffee</em> which is going to contain our tests.</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">should</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'should'</span> <span class="nx">describe</span> <span class="s">'Sofas API'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">describe</span> <span class="s">'/sofas'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">it</span> <span class="s">'should return all sofas with GET'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="c1"># TODO: write test</span> <span class="p">(</span><span class="no">false</span><span class="p">).</span><span class="na">should</span><span class="p">.</span><span class="na">be</span><span class="p">.</span><span class="na">true</span><span class="p">()</span> <span class="nx">describe</span> <span class="s">'/sofas/:sofa'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">it</span> <span class="s">'should return a particular sofa with GET'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="c1"># TODO: write test</span> <span class="p">(</span><span class="no">false</span><span class="p">).</span><span class="na">should</span><span class="p">.</span><span class="na">be</span><span class="p">.</span><span class="na">true</span><span class="p">()</span> </code></pre> </div> <p>If you run the tests right now, you should see similar output:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test </span>Sofas API /sofas 1<span class="o">)</span> should <span class="k">return </span>all sofas with GET /sofas/:sofa 2<span class="o">)</span> should <span class="k">return </span>a particular sofa with GET 0 passing <span class="o">(</span>30ms<span class="o">)</span> 2 failing 1<span class="o">)</span> Sofas API /sofas should <span class="k">return </span>all sofas with GET: AssertionError: expected <span class="nb">false </span>to be <span class="nb">true </span>at Context.&lt;anonymous&gt; <span class="o">(</span>specs/sofas.spec.coffee:8:7<span class="o">)</span> 2<span class="o">)</span> Sofas API /sofas/:sofa should <span class="k">return </span>a particular sofa with GET: AssertionError: expected <span class="nb">false </span>to be <span class="nb">true </span>at Context.&lt;anonymous&gt; <span class="o">(</span>specs/sofas.spec.coffee:13:7<span class="o">)</span> Message: 2 tests failed. </code></pre> </div> <h2 id="implementation">Implementation</h2> <p>Sofas will be stored in CouchDB with the following structure:</p> <div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nt">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CouchDB assigned id"</span><span class="p">,</span><span class="w"> </span><span class="nt">"_rev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CouchDB assigned revision"</span><span class="p">,</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Name of the sofa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"price"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Price of the sofa"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>With this in mind, let’s build a module for accessing the database.</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">nano</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'nano'</span> <span class="nx">Q</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'q'</span> <span class="k">class</span> <span class="nx">DAL</span> <span class="na">constructor</span><span class="o">:</span> <span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="vi">@</span><span class="na">database</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="vi">@</span><span class="na">conn</span> <span class="o">=</span> <span class="nx">nano</span><span class="p">(</span><span class="s">"http://localhost:</span><span class="si">#{</span><span class="nx">port</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="c1"># Returns a promise reseting database</span> <span class="na">resetDatabase</span><span class="o">:</span> <span class="p">(</span><span class="nx">withSamples</span><span class="o">=</span><span class="no">false</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">docs</span> <span class="o">=</span> <span class="p">[</span><span class="vi">@</span><span class="na">_sofaViews</span><span class="p">]</span> <span class="k">if</span> <span class="nx">withSamples</span> <span class="nx">docs</span> <span class="o">=</span> <span class="nx">docs</span><span class="p">.</span><span class="na">concat</span> <span class="vi">@</span><span class="na">_sofaSamples</span> <span class="vi">@</span><span class="na">_deleteDb</span><span class="p">()</span> <span class="p">.</span><span class="na">then</span> <span class="o">=&gt;</span> <span class="vi">@</span><span class="na">_createDb</span><span class="p">()</span> <span class="p">.</span><span class="na">then</span> <span class="o">=&gt;</span> <span class="nx">Q</span><span class="p">.</span><span class="na">all</span><span class="p">(</span><span class="nx">docs</span><span class="p">.</span><span class="na">map</span> <span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="vi">@</span><span class="na">_insertDoc</span><span class="p">(</span><span class="nx">d</span><span class="p">))</span> <span class="c1"># Returns a promise of sofas</span> <span class="c1"># If you provide the id- you'll get an array with a particular sofa in it</span> <span class="na">getSofas</span><span class="o">:</span> <span class="p">(</span><span class="nx">id</span><span class="o">=</span><span class="no">null</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{}</span> <span class="nx">params</span><span class="p">.</span><span class="na">key</span> <span class="o">=</span> <span class="nx">id</span> <span class="k">if</span> <span class="nx">id</span><span class="o">?</span> <span class="vi">@</span><span class="na">_view</span><span class="p">(</span><span class="s">'sofas'</span><span class="p">,</span> <span class="s">'all'</span><span class="p">,</span> <span class="nx">params</span><span class="p">)</span> <span class="p">.</span><span class="na">then</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">res</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="nx">sofas</span> <span class="o">=</span> <span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="na">value</span> <span class="k">for</span> <span class="nx">row</span> <span class="k">in</span> <span class="nx">body</span><span class="p">.</span><span class="na">rows</span><span class="p">)</span> <span class="k">return</span> <span class="nx">sofas</span> <span class="na">_sofaViews</span><span class="o">:</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'_design/sofas'</span> <span class="na">views</span><span class="o">:</span> <span class="na">all</span><span class="o">:</span> <span class="na">map</span><span class="o">:</span> <span class="p">(</span><span class="nx">doc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">if</span> <span class="nx">doc</span><span class="p">.</span><span class="na">type</span> <span class="o">is</span> <span class="s">'Sofa'</span> <span class="nx">emit</span> <span class="nx">doc</span><span class="p">.</span><span class="na">_id</span><span class="p">,</span> <span class="nx">doc</span> <span class="na">_sofaSamples</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa1'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa1'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">400</span> <span class="p">}</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa2'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa2'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">300</span> <span class="p">}</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa3'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa3'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">500</span> <span class="p">}</span> <span class="p">]</span> <span class="c1"># Returns promise of creating database</span> <span class="na">_createDb</span><span class="o">:</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">db</span><span class="p">.</span><span class="na">create</span> <span class="vi">@</span><span class="na">database</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of deleting database</span> <span class="na">_deleteDb</span><span class="o">:</span> <span class="p">(</span><span class="nx">ignoreMissing</span><span class="o">=</span><span class="no">true</span><span class="p">)</span><span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">db</span><span class="p">.</span><span class="na">destroy</span> <span class="vi">@</span><span class="na">database</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">err</span> <span class="o">=</span> <span class="no">null</span> <span class="k">if</span> <span class="nx">err</span><span class="o">?</span> <span class="o">and</span> <span class="nx">err</span><span class="p">.</span><span class="na">statusCode</span> <span class="o">is</span> <span class="mi">404</span> <span class="o">and</span> <span class="nx">ignoreMissing</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of inserting a doc</span> <span class="na">_insertDoc</span><span class="o">:</span> <span class="p">(</span><span class="nx">doc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">use</span><span class="p">(</span><span class="vi">@</span><span class="na">database</span><span class="p">).</span><span class="na">insert</span> <span class="nx">doc</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of CouchDB view</span> <span class="na">_view</span><span class="o">:</span> <span class="p">(</span><span class="nx">designDoc</span><span class="p">,</span> <span class="nx">view</span><span class="p">,</span> <span class="nx">params</span><span class="o">=</span><span class="p">{})</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">use</span><span class="p">(</span><span class="vi">@</span><span class="na">database</span><span class="p">).</span><span class="na">view</span> <span class="nx">designDoc</span><span class="p">,</span> <span class="nx">view</span><span class="p">,</span> <span class="nx">params</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="nx">module</span><span class="p">.</span><span class="na">exports</span> <span class="o">=</span> <span class="nx">DAL</span> </code></pre> </div> <p>What’s missing is the endpoint and test implementation. Let’s start with the tests.</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/specs/sofas.spec.coffee b/specs/sofas.spec.coffee index 8c1eb84..efbff58 100644 </span><span class="gd">--- a/specs/sofas.spec.coffee </span><span class="gi">+++ b/specs/sofas.spec.coffee </span><span class="gu">@@ -1,13 +1,54 @@ </span><span class="gd">-should = require 'should' </span><span class="gi">+should = require 'should' +DAL = require '../dal' +request = require 'supertest' +request = request "http://localhost:#{process.env.PORT}" +server = require '../server' + +app = null </span> describe 'Sofas API', -&gt; <span class="gi">+ beforeEach (done) -&gt; + # Reset database and start express server before each test + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + dal.resetDatabase(true) + .then -&gt; + app = server.listen done + .fail (err) -&gt; done err + + # Close express server after each test + afterEach (done) -&gt; + app.close done + + </span> describe '/sofas', -&gt; <span class="gd">- it 'should return all sofas with GET', -&gt; - # TODO: write test - (false).should.be.true() </span><span class="gi">+ it 'should return all sofas with GET', (done) -&gt; + request + .get '/sofas' + .expect 200 + .expect (res) -&gt; + sofas = res.body + sofas.should.have.length(3) + for s in sofas + s.should.have.property('_id') + s.should.have.property('_rev') + s.should.have.property('name') + s.should.have.property('price') + + .end done + + </span> describe '/sofas/:sofa', -&gt; <span class="gd">- it 'should return a particular sofa with GET', -&gt; - # TODO: write test - (false).should.be.true() </span><span class="gi">+ it 'should return a particular sofa with GET', (done) -&gt; + request + .get '/sofas/sofa1' + .expect 200 + .expect (res) -&gt; + sofa = res.body + sofa.should.have.property('_id') + sofa.should.have.property('_rev') + sofa.should.have.property('name') + sofa.should.have.property('price') + + .end done </span></code></pre> </div> <p>Now the endpoints:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/server.coffee b/server.coffee index 98f1918..a0d30ce 100644 </span><span class="gd">--- a/server.coffee </span><span class="gi">+++ b/server.coffee </span><span class="gu">@@ -1,16 +1,27 @@ </span> express = require 'express' <span class="gi">+DAL = require './dal' </span> <span class="gi">+dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) </span> app = express() app.get '/sofas', (req, res) -&gt; <span class="gd">- throw new Error 'not implemented' </span><span class="gi">+ dal.getSofas() + .then (sofas) -&gt; + res.json(sofas) + .fail (err) -&gt; res.status(err.statusCode).send() </span> app.get '/sofas/:sofa', (req, res) -&gt; <span class="gd">- throw new Error 'not implemented' </span><span class="gi">+ dal.getSofas(req.params.sofa) + .then (sofas) -&gt; + if sofas.length isnt 1 + res.status(404).send() + else + res.json(sofas[0]) + .fail (err) -&gt; res.status(err.statusCode).send() </span> exports.listen = (callback) -&gt; </code></pre> </div> <p>At this point we can execute <em>gulp test-int</em> command to run tests on real CouchDB:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test</span>-int Sofas API /sofas ✓ should <span class="k">return </span>all sofas with GET /sofas/:sofa ✓ should <span class="k">return </span>a particular sofa with GET 2 passing <span class="o">(</span>204ms<span class="o">)</span> </code></pre> </div> <h2 id="mock-couch-tests">mock-couch tests</h2> <p>So how do we use mock-couch for this? Mock-couch is an HTTP server based on <a href="http://mcavage.me/node-restify/">Restify</a>. Note that <a href="https://nodejs.org/">Node.js</a> runtime is single-threaded therefore we won’t be able to run both express and mock-couch at the same time. However, it is possible to run mock-couch on another process.</p> <p>Node provides a couple of ways to create processes out of the box. We’ll use one called <a href="https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options">forking</a>: not only we’ll be able to control the lifecycle of the forked child process but we’ll also have the ability to send and receive messages between parent and the child.</p> <p>OK, less talk and more forking. We’ll do the forking in <em>gulp test</em> task:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/gulpfile.coffee b/gulpfile.coffee index cbd61b5..1200834 100644 </span><span class="gd">--- a/gulpfile.coffee </span><span class="gi">+++ b/gulpfile.coffee </span><span class="gu">@@ -6,14 +6,48 @@ mocha = require 'gulp-mocha' </span> sources = tests: './specs/**/*.spec.coffee' <span class="gi">+ </span> # Task to run tests with mock-couch backend gulp.task 'test', -&gt; process.env.NODE_ENV = 'test' process.env.PORT = 3010 process.env.COUCH_PORT = 5987 process.env.DATABASE = 'sofas_test' <span class="gd">- gulp.src(sources.tests) - .pipe(mocha()) </span><span class="gi">+ + fork = require('child_process').fork + couchProcess = fork('node_modules/gulp/bin/gulp.js', ['mock-couch'], {cwd: __dirname}) + couchProcess.on 'message', (msg) =&gt; + if msg is 'listening' + gulp.src(sources.tests) + .pipe(mocha()) + .once('error', (err) -&gt; + process.exit(1) + ) + .once('end', -&gt; + process.exit() + ) + + process.on 'exit', -&gt; + couchProcess.kill() + +gulp.task 'mock-couch', -&gt; + process.env.NODE_ENV = 'test' + process.env.PORT = 3010 + process.env.COUCH_PORT = 5987 + process.env.DATABASE = 'sofas_test' + + mockCouch = require 'mock-couch' + couchdb = mockCouch.createServer() + DAL = require './dal' + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + docs = [dal._sofaViews] + docs = docs.concat dal._sofaSamples + + couchdb.addDB process.env.DATABASE, docs + + couchdb.listen process.env.COUCH_PORT, -&gt; + process.send 'listening' + </span> # Task to run integration tests (real CouchDB backend) gulp.task 'test-int', -&gt; </code></pre> </div> <p>Also, we need a slight change to the test setup:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/specs/sofas.spec.coffee b/specs/sofas.spec.coffee index efbff58..93e7bd2 100644 </span><span class="gd">--- a/specs/sofas.spec.coffee </span><span class="gi">+++ b/specs/sofas.spec.coffee </span><span class="gu">@@ -9,12 +9,17 @@ app = null </span> describe 'Sofas API', -&gt; beforeEach (done) -&gt; <span class="gd">- # Reset database and start express server before each test - dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) - dal.resetDatabase(true) - .then -&gt; - app = server.listen done - .fail (err) -&gt; done err </span><span class="gi">+ + if process.env.NODE_ENV is 'test-int' + # Reset database and start express server before each test + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + dal.resetDatabase(true) + .then -&gt; + app = server.listen done + .fail (err) -&gt; done err + else + app = server.listen done + </span> # Close express server after each test afterEach (done) -&gt; </code></pre> </div> <h2 id="moment-of-truth">Moment of Truth</h2> <p>If you try running <code class="highlighter-rouge">gulp test</code> right now, you should see similar output:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test </span>Sofas API /sofas ✓ should <span class="k">return </span>all sofas with GET <span class="o">(</span>125ms<span class="o">)</span> /sofas/:sofa ✓ should <span class="k">return </span>a particular sofa with GET <span class="o">(</span>41ms<span class="o">)</span> 2 passing <span class="o">(</span>178ms<span class="o">)</span> </code></pre> </div> <p>As you can see the tests completed slightly faster than with real CouchDB. In a small code base like this, it’s hardly worth switching to mock-couch, if execution speed is your only motivation. However, with bigger codebases, the speed improvement can become very apparent.</p> <p>Besides speed, there’s another benefit to this approach: you don’t actually need CouchDB to test your project. The simplified build process is especially useful with Continuous Integration platforms like <a href="http://travis-ci.org/">Travis CI</a> and <a href="https://codeship.com">Codeship</a>.</p> <h2 id="resources">Resources</h2> <ul> <li><a href="https://github.com/reederz/express-mock-couch-showcase">Code on GitHub</a></li> <li><a href="https://github.com/chris-l/mock-couch">mock-couch</a></li> </ul> Testing Express.js + CouchDB Applications with mock-couch 2015-07-05T22:30:00+02:00 https://fadeit.dk/blog /2015/07/05/testing-express-couchdb-apps-with-mock-couch <h2 id="prerequisites">Prerequisites</h2> <p>The code in this post is based upon <a href="http://coffeescript.org/">CoffeeScript</a>, <a href="http://mochajs.org/">Mocha</a> and <a href="http://gulpjs.com/">Gulp</a> but it should be easily adaptable to other stacks (e.g. <a href="https://www.javascript.com/">JavaScript</a>, <a href="https://jasmine.github.io/">Jasmine</a> and <a href="http://gruntjs.com/">Grunt</a>).</p> <h2 id="intro">Intro</h2> <p>Recently, I worked on a <a href="http://expressjs.com/">Express.js</a> based RESTful API with <a href="https://couchdb.apache.org/">CouchDB</a> storage. For various reasons, I mostly had end-to-end tests (testing HTTP endpoints) and very few unit tests. In the tests, I would rebuild the database before every <code class="highlighter-rouge">describe</code> block. This worked quite well but at one point I noticed that my feedback loops were beginning to get tediously slow (~30 seconds to run the full test suite). Thankfully, I discovered <a href="https://github.com/chris-l/mock-couch">mock-couch</a>: in-memory storage with CouchDB API. The switch was rather easy and, as a result, my test suite would finish in less than 10 seconds (even when reseting test data before each test).</p> <h2 id="setup">Setup</h2> <p>Our sample API will expose functionality to access a database of sofa’s. Let’s set it up.</p> <p>Structure and dependencies</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>mkdir -p sofa-api/specs <span class="nb">cd </span>sofa-api npm install mock-couch coffee-script gulp gulp-mocha should supertest express nano q </code></pre> </div> <p>gulpfile.coffee</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">require</span> <span class="s">'coffee-script/register'</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'gulp'</span> <span class="nx">mocha</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'gulp-mocha'</span> <span class="nx">sources</span> <span class="o">=</span> <span class="na">tests</span><span class="o">:</span> <span class="s">'./specs/**/*.spec.coffee'</span> <span class="c1"># Task to run tests with mock-couch backend</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'test'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'test'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3010</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5987</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas_test'</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">src</span><span class="p">(</span><span class="nx">sources</span><span class="p">.</span><span class="na">tests</span><span class="p">)</span> <span class="p">.</span><span class="na">pipe</span><span class="p">(</span><span class="nx">mocha</span><span class="p">())</span> <span class="c1"># Task to run integration tests (real CouchDB backend)</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'test-int'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'test-int'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3010</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5984</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas_test'</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">src</span><span class="p">(</span><span class="nx">sources</span><span class="p">.</span><span class="na">tests</span><span class="p">)</span> <span class="p">.</span><span class="na">pipe</span><span class="p">(</span><span class="nx">mocha</span><span class="p">())</span> <span class="c1"># Task to run our server</span> <span class="nx">gulp</span><span class="p">.</span><span class="na">task</span> <span class="s">'default'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">NODE_ENV</span> <span class="o">=</span> <span class="s">'development'</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="o">=</span> <span class="mi">3000</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">COUCH_PORT</span> <span class="o">=</span> <span class="mi">5984</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">DATABASE</span> <span class="o">=</span> <span class="s">'sofas'</span> <span class="nx">server</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'./server'</span> <span class="nx">server</span><span class="p">.</span><span class="na">listen</span><span class="p">()</span> </code></pre> </div> <p>server.coffee</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'express'</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">()</span> <span class="nx">app</span><span class="p">.</span><span class="na">get</span> <span class="s">'/sofas'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="s">'not implemented'</span> <span class="nx">app</span><span class="p">.</span><span class="na">get</span> <span class="s">'/sofas/:sofa'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="s">'not implemented'</span> <span class="nx">exports</span><span class="p">.</span><span class="na">listen</span> <span class="o">=</span> <span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="na">env</span><span class="p">.</span><span class="na">PORT</span> <span class="nx">app</span><span class="p">.</span><span class="na">listen</span> <span class="nx">port</span><span class="p">,</span> <span class="nx">callback</span> </code></pre> </div> <p>Let’s create file <em>specs/sofas.spec.coffee</em> which is going to contain our tests.</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">should</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'should'</span> <span class="nx">describe</span> <span class="s">'Sofas API'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">describe</span> <span class="s">'/sofas'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">it</span> <span class="s">'should return all sofas with GET'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="c1"># TODO: write test</span> <span class="p">(</span><span class="no">false</span><span class="p">).</span><span class="na">should</span><span class="p">.</span><span class="na">be</span><span class="p">.</span><span class="na">true</span><span class="p">()</span> <span class="nx">describe</span> <span class="s">'/sofas/:sofa'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="nx">it</span> <span class="s">'should return a particular sofa with GET'</span><span class="p">,</span> <span class="o">-&gt;</span> <span class="c1"># TODO: write test</span> <span class="p">(</span><span class="no">false</span><span class="p">).</span><span class="na">should</span><span class="p">.</span><span class="na">be</span><span class="p">.</span><span class="na">true</span><span class="p">()</span> </code></pre> </div> <p>If you run the tests right now, you should see similar output:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test </span>Sofas API /sofas 1<span class="o">)</span> should <span class="k">return </span>all sofas with GET /sofas/:sofa 2<span class="o">)</span> should <span class="k">return </span>a particular sofa with GET 0 passing <span class="o">(</span>30ms<span class="o">)</span> 2 failing 1<span class="o">)</span> Sofas API /sofas should <span class="k">return </span>all sofas with GET: AssertionError: expected <span class="nb">false </span>to be <span class="nb">true </span>at Context.&lt;anonymous&gt; <span class="o">(</span>specs/sofas.spec.coffee:8:7<span class="o">)</span> 2<span class="o">)</span> Sofas API /sofas/:sofa should <span class="k">return </span>a particular sofa with GET: AssertionError: expected <span class="nb">false </span>to be <span class="nb">true </span>at Context.&lt;anonymous&gt; <span class="o">(</span>specs/sofas.spec.coffee:13:7<span class="o">)</span> Message: 2 tests failed. </code></pre> </div> <h2 id="implementation">Implementation</h2> <p>Sofas will be stored in CouchDB with the following structure:</p> <div class="language-json highlighter-rouge"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nt">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CouchDB assigned id"</span><span class="p">,</span><span class="w"> </span><span class="nt">"_rev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CouchDB assigned revision"</span><span class="p">,</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Name of the sofa"</span><span class="p">,</span><span class="w"> </span><span class="nt">"price"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Price of the sofa"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>With this in mind, let’s build a module for accessing the database.</p> <div class="language-coffeescript highlighter-rouge"><pre class="highlight"><code><span class="nx">nano</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'nano'</span> <span class="nx">Q</span> <span class="o">=</span> <span class="nx">require</span> <span class="s">'q'</span> <span class="k">class</span> <span class="nx">DAL</span> <span class="na">constructor</span><span class="o">:</span> <span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="vi">@</span><span class="na">database</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="vi">@</span><span class="na">conn</span> <span class="o">=</span> <span class="nx">nano</span><span class="p">(</span><span class="s">"http://localhost:</span><span class="si">#{</span><span class="nx">port</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="c1"># Returns a promise reseting database</span> <span class="na">resetDatabase</span><span class="o">:</span> <span class="p">(</span><span class="nx">withSamples</span><span class="o">=</span><span class="no">false</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">docs</span> <span class="o">=</span> <span class="p">[</span><span class="vi">@</span><span class="na">_sofaViews</span><span class="p">]</span> <span class="k">if</span> <span class="nx">withSamples</span> <span class="nx">docs</span> <span class="o">=</span> <span class="nx">docs</span><span class="p">.</span><span class="na">concat</span> <span class="vi">@</span><span class="na">_sofaSamples</span> <span class="vi">@</span><span class="na">_deleteDb</span><span class="p">()</span> <span class="p">.</span><span class="na">then</span> <span class="o">=&gt;</span> <span class="vi">@</span><span class="na">_createDb</span><span class="p">()</span> <span class="p">.</span><span class="na">then</span> <span class="o">=&gt;</span> <span class="nx">Q</span><span class="p">.</span><span class="na">all</span><span class="p">(</span><span class="nx">docs</span><span class="p">.</span><span class="na">map</span> <span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="vi">@</span><span class="na">_insertDoc</span><span class="p">(</span><span class="nx">d</span><span class="p">))</span> <span class="c1"># Returns a promise of sofas</span> <span class="c1"># If you provide the id- you'll get an array with a particular sofa in it</span> <span class="na">getSofas</span><span class="o">:</span> <span class="p">(</span><span class="nx">id</span><span class="o">=</span><span class="no">null</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{}</span> <span class="nx">params</span><span class="p">.</span><span class="na">key</span> <span class="o">=</span> <span class="nx">id</span> <span class="k">if</span> <span class="nx">id</span><span class="o">?</span> <span class="vi">@</span><span class="na">_view</span><span class="p">(</span><span class="s">'sofas'</span><span class="p">,</span> <span class="s">'all'</span><span class="p">,</span> <span class="nx">params</span><span class="p">)</span> <span class="p">.</span><span class="na">then</span> <span class="p">(</span><span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">res</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="nx">sofas</span> <span class="o">=</span> <span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="na">value</span> <span class="k">for</span> <span class="nx">row</span> <span class="k">in</span> <span class="nx">body</span><span class="p">.</span><span class="na">rows</span><span class="p">)</span> <span class="k">return</span> <span class="nx">sofas</span> <span class="na">_sofaViews</span><span class="o">:</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'_design/sofas'</span> <span class="na">views</span><span class="o">:</span> <span class="na">all</span><span class="o">:</span> <span class="na">map</span><span class="o">:</span> <span class="p">(</span><span class="nx">doc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">if</span> <span class="nx">doc</span><span class="p">.</span><span class="na">type</span> <span class="o">is</span> <span class="s">'Sofa'</span> <span class="nx">emit</span> <span class="nx">doc</span><span class="p">.</span><span class="na">_id</span><span class="p">,</span> <span class="nx">doc</span> <span class="na">_sofaSamples</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa1'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa1'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">400</span> <span class="p">}</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa2'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa2'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">300</span> <span class="p">}</span> <span class="p">{</span> <span class="na">_id</span><span class="o">:</span> <span class="s">'sofa3'</span> <span class="na">type</span><span class="o">:</span> <span class="s">'Sofa'</span> <span class="na">name</span><span class="o">:</span> <span class="s">'Sofa3'</span> <span class="na">price</span><span class="o">:</span> <span class="mi">500</span> <span class="p">}</span> <span class="p">]</span> <span class="c1"># Returns promise of creating database</span> <span class="na">_createDb</span><span class="o">:</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">db</span><span class="p">.</span><span class="na">create</span> <span class="vi">@</span><span class="na">database</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of deleting database</span> <span class="na">_deleteDb</span><span class="o">:</span> <span class="p">(</span><span class="nx">ignoreMissing</span><span class="o">=</span><span class="no">true</span><span class="p">)</span><span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">db</span><span class="p">.</span><span class="na">destroy</span> <span class="vi">@</span><span class="na">database</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">err</span> <span class="o">=</span> <span class="no">null</span> <span class="k">if</span> <span class="nx">err</span><span class="o">?</span> <span class="o">and</span> <span class="nx">err</span><span class="p">.</span><span class="na">statusCode</span> <span class="o">is</span> <span class="mi">404</span> <span class="o">and</span> <span class="nx">ignoreMissing</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of inserting a doc</span> <span class="na">_insertDoc</span><span class="o">:</span> <span class="p">(</span><span class="nx">doc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">use</span><span class="p">(</span><span class="vi">@</span><span class="na">database</span><span class="p">).</span><span class="na">insert</span> <span class="nx">doc</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="c1"># Returns promise of CouchDB view</span> <span class="na">_view</span><span class="o">:</span> <span class="p">(</span><span class="nx">designDoc</span><span class="p">,</span> <span class="nx">view</span><span class="p">,</span> <span class="nx">params</span><span class="o">=</span><span class="p">{})</span> <span class="o">-&gt;</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Q</span><span class="p">.</span><span class="na">defer</span><span class="p">()</span> <span class="vi">@</span><span class="na">conn</span><span class="p">.</span><span class="na">use</span><span class="p">(</span><span class="vi">@</span><span class="na">database</span><span class="p">).</span><span class="na">view</span> <span class="nx">designDoc</span><span class="p">,</span> <span class="nx">view</span><span class="p">,</span> <span class="nx">params</span><span class="p">,</span> <span class="nx">d</span><span class="p">.</span><span class="na">makeNodeResolver</span><span class="p">()</span> <span class="nx">d</span><span class="p">.</span><span class="na">promise</span> <span class="nx">module</span><span class="p">.</span><span class="na">exports</span> <span class="o">=</span> <span class="nx">DAL</span> </code></pre> </div> <p>What’s missing is the endpoint and test implementation. Let’s start with the tests.</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/specs/sofas.spec.coffee b/specs/sofas.spec.coffee index 8c1eb84..efbff58 100644 </span><span class="gd">--- a/specs/sofas.spec.coffee </span><span class="gi">+++ b/specs/sofas.spec.coffee </span><span class="gu">@@ -1,13 +1,54 @@ </span><span class="gd">-should = require 'should' </span><span class="gi">+should = require 'should' +DAL = require '../dal' +request = require 'supertest' +request = request "http://localhost:#{process.env.PORT}" +server = require '../server' + +app = null </span> describe 'Sofas API', -&gt; <span class="gi">+ beforeEach (done) -&gt; + # Reset database and start express server before each test + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + dal.resetDatabase(true) + .then -&gt; + app = server.listen done + .fail (err) -&gt; done err + + # Close express server after each test + afterEach (done) -&gt; + app.close done + + </span> describe '/sofas', -&gt; <span class="gd">- it 'should return all sofas with GET', -&gt; - # TODO: write test - (false).should.be.true() </span><span class="gi">+ it 'should return all sofas with GET', (done) -&gt; + request + .get '/sofas' + .expect 200 + .expect (res) -&gt; + sofas = res.body + sofas.should.have.length(3) + for s in sofas + s.should.have.property('_id') + s.should.have.property('_rev') + s.should.have.property('name') + s.should.have.property('price') + + .end done + + </span> describe '/sofas/:sofa', -&gt; <span class="gd">- it 'should return a particular sofa with GET', -&gt; - # TODO: write test - (false).should.be.true() </span><span class="gi">+ it 'should return a particular sofa with GET', (done) -&gt; + request + .get '/sofas/sofa1' + .expect 200 + .expect (res) -&gt; + sofa = res.body + sofa.should.have.property('_id') + sofa.should.have.property('_rev') + sofa.should.have.property('name') + sofa.should.have.property('price') + + .end done </span></code></pre> </div> <p>Now the endpoints:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/server.coffee b/server.coffee index 98f1918..a0d30ce 100644 </span><span class="gd">--- a/server.coffee </span><span class="gi">+++ b/server.coffee </span><span class="gu">@@ -1,16 +1,27 @@ </span> express = require 'express' <span class="gi">+DAL = require './dal' </span> <span class="gi">+dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) </span> app = express() app.get '/sofas', (req, res) -&gt; <span class="gd">- throw new Error 'not implemented' </span><span class="gi">+ dal.getSofas() + .then (sofas) -&gt; + res.json(sofas) + .fail (err) -&gt; res.status(err.statusCode).send() </span> app.get '/sofas/:sofa', (req, res) -&gt; <span class="gd">- throw new Error 'not implemented' </span><span class="gi">+ dal.getSofas(req.params.sofa) + .then (sofas) -&gt; + if sofas.length isnt 1 + res.status(404).send() + else + res.json(sofas[0]) + .fail (err) -&gt; res.status(err.statusCode).send() </span> exports.listen = (callback) -&gt; </code></pre> </div> <p>At this point we can execute <em>gulp test-int</em> command to run tests on real CouchDB:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test</span>-int Sofas API /sofas ✓ should <span class="k">return </span>all sofas with GET /sofas/:sofa ✓ should <span class="k">return </span>a particular sofa with GET 2 passing <span class="o">(</span>204ms<span class="o">)</span> </code></pre> </div> <h2 id="mock-couch-tests">mock-couch tests</h2> <p>So how do we use mock-couch for this? Mock-couch is an HTTP server based on <a href="http://mcavage.me/node-restify/">Restify</a>. Note that <a href="https://nodejs.org/">Node.js</a> runtime is single-threaded therefore we won’t be able to run both express and mock-couch at the same time. However, it is possible to run mock-couch on another process.</p> <p>Node provides a couple of ways to create processes out of the box. We’ll use one called <a href="https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options">forking</a>: not only we’ll be able to control the lifecycle of the forked child process but we’ll also have the ability to send and receive messages between parent and the child.</p> <p>OK, less talk and more forking. We’ll do the forking in <em>gulp test</em> task:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/gulpfile.coffee b/gulpfile.coffee index cbd61b5..1200834 100644 </span><span class="gd">--- a/gulpfile.coffee </span><span class="gi">+++ b/gulpfile.coffee </span><span class="gu">@@ -6,14 +6,48 @@ mocha = require 'gulp-mocha' </span> sources = tests: './specs/**/*.spec.coffee' <span class="gi">+ </span> # Task to run tests with mock-couch backend gulp.task 'test', -&gt; process.env.NODE_ENV = 'test' process.env.PORT = 3010 process.env.COUCH_PORT = 5987 process.env.DATABASE = 'sofas_test' <span class="gd">- gulp.src(sources.tests) - .pipe(mocha()) </span><span class="gi">+ + fork = require('child_process').fork + couchProcess = fork('node_modules/gulp/bin/gulp.js', ['mock-couch'], {cwd: __dirname}) + couchProcess.on 'message', (msg) =&gt; + if msg is 'listening' + gulp.src(sources.tests) + .pipe(mocha()) + .once('error', (err) -&gt; + process.exit(1) + ) + .once('end', -&gt; + process.exit() + ) + + process.on 'exit', -&gt; + couchProcess.kill() + +gulp.task 'mock-couch', -&gt; + process.env.NODE_ENV = 'test' + process.env.PORT = 3010 + process.env.COUCH_PORT = 5987 + process.env.DATABASE = 'sofas_test' + + mockCouch = require 'mock-couch' + couchdb = mockCouch.createServer() + DAL = require './dal' + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + docs = [dal._sofaViews] + docs = docs.concat dal._sofaSamples + + couchdb.addDB process.env.DATABASE, docs + + couchdb.listen process.env.COUCH_PORT, -&gt; + process.send 'listening' + </span> # Task to run integration tests (real CouchDB backend) gulp.task 'test-int', -&gt; </code></pre> </div> <p>Also, we need a slight change to the test setup:</p> <div class="language-diff highlighter-rouge"><pre class="highlight"><code><span class="gh">diff --git a/specs/sofas.spec.coffee b/specs/sofas.spec.coffee index efbff58..93e7bd2 100644 </span><span class="gd">--- a/specs/sofas.spec.coffee </span><span class="gi">+++ b/specs/sofas.spec.coffee </span><span class="gu">@@ -9,12 +9,17 @@ app = null </span> describe 'Sofas API', -&gt; beforeEach (done) -&gt; <span class="gd">- # Reset database and start express server before each test - dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) - dal.resetDatabase(true) - .then -&gt; - app = server.listen done - .fail (err) -&gt; done err </span><span class="gi">+ + if process.env.NODE_ENV is 'test-int' + # Reset database and start express server before each test + dal = new DAL(process.env.COUCH_PORT, process.env.DATABASE) + dal.resetDatabase(true) + .then -&gt; + app = server.listen done + .fail (err) -&gt; done err + else + app = server.listen done + </span> # Close express server after each test afterEach (done) -&gt; </code></pre> </div> <h2 id="moment-of-truth">Moment of Truth</h2> <p>If you try running <code class="highlighter-rouge">gulp test</code> right now, you should see similar output:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>gulp <span class="nb">test </span>Sofas API /sofas ✓ should <span class="k">return </span>all sofas with GET <span class="o">(</span>125ms<span class="o">)</span> /sofas/:sofa ✓ should <span class="k">return </span>a particular sofa with GET <span class="o">(</span>41ms<span class="o">)</span> 2 passing <span class="o">(</span>178ms<span class="o">)</span> </code></pre> </div> <p>As you can see the tests completed slightly faster than with real CouchDB. In a small code base like this, it’s hardly worth switching to mock-couch, if execution speed is your only motivation. However, with bigger codebases, the speed improvement can become very apparent.</p> <p>Besides speed, there’s another benefit to this approach: you don’t actually need CouchDB to test your project. The simplified build process is especially useful with Continuous Integration platforms like <a href="http://travis-ci.org/">Travis CI</a> and <a href="https://codeship.com">Codeship</a>.</p> <h2 id="resources">Resources</h2> <ul> <li><a href="https://github.com/reederz/express-mock-couch-showcase">Code on GitHub</a></li> <li><a href="https://github.com/chris-l/mock-couch">mock-couch</a></li> </ul> Socket.io with AngularJS boilerplate 2015-05-29T13:18:15+02:00 https://fadeit.dk/blog /2015/05/29/socketio-angular-ultra-simple-boilerplate <p><a href="http://socket.io">Socket.io</a> has truly been a godsend to making realtime applicaions. What used to require writing complex code, ajax polling and other tricks of the trade is now a few lines of code. And it’s fast too - in fact we have a situation where client sends a POST request to python server, which in turn is passed to node server via <a href="http://redis.io">redis</a> <a href="http://redis.io/topics/pubsub">pub/sub</a> and that in turn send a message to the client with socket.io. The interesting bit is that although the socket.io round-trip involves more components, it delivers the message back to client before the response is received. While pure HTTP implementation with network latency in the mix might win the race, it is still a valid contender.</p> <p>I often find myself experimenting on some concept where I need a quick and simple boilerplate to get me going - setting it all up takes time after-all. There are great demos out there, but they often cover more ground than necessary, what I am mostly interested in is the bare minimum working backbone that I can build upon. So here is a stripped-down chat application in just 2 files, doesn’t even contain an http server!</p> <h2 id="serverjs">Server.js</h2> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">io</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'socket.io'</span><span class="p">).</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">);</span> <span class="nx">io</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'connection'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">socket</span><span class="p">){</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'message'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">msg</span><span class="p">){</span> <span class="nx">io</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s1">'message'</span><span class="p">,</span> <span class="nx">msg</span><span class="p">);</span> <span class="p">});</span> <span class="p">});</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Server up!"</span><span class="p">);</span> </code></pre> </div> <h2 id="indexhtml">Index.html</h2> <p>Since Angular is not aware of socket receiving a message, we need to wrap the <code class="highlighter-rouge">$scope</code> update in an <code class="highlighter-rouge">$apply</code> function so that angular would pick it up.</p> <div class="language-html highlighter-rouge"><pre class="highlight"><code><span class="cp">&lt;!doctype html&gt;</span> <span class="nt">&lt;html</span> <span class="na">ng-app=</span><span class="s">"myApp"</span><span class="nt">&gt;</span> <span class="nt">&lt;head&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.js"</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="nt">&lt;/head&gt;</span> <span class="nt">&lt;body</span> <span class="na">ng-controller=</span><span class="s">"myAppCtrl"</span><span class="nt">&gt;</span> <span class="nt">&lt;textarea</span> <span class="na">ng-model=</span><span class="s">"messages"</span> <span class="na">rows=</span><span class="s">15</span> <span class="na">cols=</span><span class="s">80</span><span class="nt">&gt;&lt;/textarea&gt;&lt;br/&gt;</span> <span class="nt">&lt;input</span> <span class="na">ng-model=</span><span class="s">"message"</span><span class="nt">&gt;&lt;/input&gt;</span> <span class="nt">&lt;button</span> <span class="na">ng-click=</span><span class="s">"send()"</span><span class="nt">&gt;</span>Send<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;script&gt;</span> <span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s2">"myApp"</span><span class="p">,</span> <span class="p">[]);</span> <span class="nx">app</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="s2">"myAppCtrl"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">$scope</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">socket</span> <span class="o">=</span> <span class="nx">io</span><span class="p">(</span><span class="s1">'http://localhost:3000'</span><span class="p">);</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">messages</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">send</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s1">'message'</span><span class="p">,</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">message</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span> <span class="p">}</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'message'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">message</span><span class="p">){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">$apply</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">messages</span> <span class="o">+=</span> <span class="nx">message</span> <span class="o">+</span> <span class="s2">"\n"</span><span class="p">;</span> <span class="p">});</span> <span class="p">});</span> <span class="p">});</span> <span class="nt">&lt;/script&gt;</span> <span class="nt">&lt;/body&gt;</span> <span class="nt">&lt;/html&gt;</span> </code></pre> </div> <h2 id="setup">Setup</h2> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>npm install socket.io node server.js </code></pre> </div> Proxy Custom Socket.IO Namespaces with NGINX 2015-05-21T22:30:00+02:00 https://fadeit.dk/blog /2015/05/21/socketio-namespaces-and-nginx <p>A while ago I implemented a chat server based on <a href="http://socket.io/">Socket.IO</a> for <a href="https://hopper.dk">hopper.dk</a>. In addition to the chat server, the system already had 2 HTTP servers: <a href="http://gunicorn.org/">Gunicorn</a> for our RESTful API and <a href="http://nginx.org/">NGINX</a> for serving <a href="https://angularjs.org/">AngularJS</a> frontend. To make SSL management easier I decided to put all 3 servers behind a single NGINX host. It all went pretty smoothly except for 1 thing- my chat server with a custom namespace was unreachable through NGINX.</p> <p>Let’s illustrate this with some code</p> <h2 id="socketio-server">Socket.IO Server</h2> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">io</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'socket.io'</span><span class="p">)();</span> <span class="nx">io</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">myNamespace</span> <span class="o">=</span> <span class="nx">io</span><span class="p">.</span><span class="nx">of</span><span class="p">(</span><span class="s1">'/my-namespace'</span><span class="p">);</span> <span class="nx">myNamespace</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'connection'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">socket</span><span class="p">){</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'someone connected'</span><span class="p">);</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s1">'news'</span><span class="p">,</span> <span class="s1">'Hi there!!'</span><span class="p">);</span> <span class="nx">myNamespace</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s1">'news'</span><span class="p">,</span> <span class="s1">'Someone joined the party!'</span><span class="p">);</span> <span class="p">});</span> </code></pre> </div> <h2 id="nginx-host">NGINX Host</h2> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>server <span class="o">{</span> listen 8080; server_name localhost; location ~/<span class="o">(</span>my-namespace<span class="o">)</span>.<span class="k">*</span><span class="nv">$ </span><span class="o">{</span> proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade <span class="nv">$http_upgrade</span>; proxy_set_header Connection <span class="s2">"upgrade"</span>; <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <h2 id="connection-attempts-fail-with-http-404">Connection attempts fail with HTTP 404</h2> <p><img src="/blog/assets/socketio-namespaces-and-nginx/fail.png" alt="Client connection attempts fail" /></p> <p>This setup results in Socket.IO client trying to establish a connection to the server with no success. After a bit of furious <em>DuckDuckGoing</em> and <em>Googling</em>, I gave up and decided to look for answers in <a href="https://github.com/Automattic/socket.io">Socket.IO’s code</a>.</p> <p>The solution is simple: in addition to proxying your custom namespace, you also have to proxy <code class="highlighter-rouge">/socket.io</code> resource to the Socket.IO server:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>server <span class="o">{</span> listen 8080; server_name localhost; location ~/<span class="o">(</span>my-namespace|socket<span class="se">\.</span>io<span class="o">)</span>.<span class="k">*</span><span class="nv">$ </span><span class="o">{</span> proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade <span class="nv">$http_upgrade</span>; proxy_set_header Connection <span class="s2">"upgrade"</span>; <span class="o">}</span> <span class="o">}</span> </code></pre> </div> <h2 id="success">Success</h2> <p><img src="/blog/assets/socketio-namespaces-and-nginx/success.png" alt="Client connection successfully established" /></p> <p>I hope this helps.</p> Getting started with Vue.js: AngularJS perspective 2015-05-14T15:14:03+02:00 https://fadeit.dk/blog /2015/05/14/getting-started-with-vuejs-angularjs-perspective <h2 id="background">Background</h2> <p>First and foremost, what is Vue.js and why should you use it?</p> <p>Vue is a library for developing interactive web interfaces. Its API is inspired by Angular &amp; Backbone, but you can read more about it in <a href="http://vuejs.org/guide/">this guide</a>. What’s more interesting is <u>why use it</u>, especially when you are familiar with Angular. First of all, it’s very flexible. It allows mixing and matching all the libraries you love. That means you can create your own little front-end stack. Secondly, it’s more snappy because it’s simpler and doesn’t use dirty checking to observe changes. On top of that, learning Vue doesn’t take much time. This will make it easy to bring in new team members.</p> <p>You can read even more about it in Vue’s <a href="http://vuejs.org/guide/faq.html">FAQ page</a>, where Vue is compared to Angular, React and more. <br /><small><strong>Updated 15 May 2015</strong></small></p> <p>I jumped on the AngularJS train more than 1 year ago and had lots of fun working with it. From August to January 2015 I had the chance to research Angular application structures. The study was in relation to performance, but it touched on many other subjects, such as component reusability and learnability.</p> <p>I am new to Vue.js, therefore this article will be about my first encounter with the library. Later on I will try to make a more comprehensive analysis.</p> <blockquote> <p>Angular <b class="title">1.*</b> and Vue.js <b class="title">0.11</b> are considered in the article. Breaking changes might come in <a href="https://github.com/vuejs/Discussion/issues/158">Vue 0.12</a>. As for Angular, version 2 will be a completely different beast.</p> </blockquote> <h2 id="vuejs-and-angularjs">Vue.js and AngularJS</h2> <p>I felt comfortable with Vue within a few days. It might be because it resembles other frameworks. It might be because of its simplicity, I couldn’t tell. To better portray the similarities &amp; differences between the two, I devised a brief comparison of some of the high-level concepts in Angular and Vue. Here’s what I ended up with:</p> <table class="fdt-table"> <thead> <tr> <th>AngularJS</th> <th>Vue.js</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td style="width: 33.33333%;"> <b class="title">Angular Modules</b> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s1">'myModule'</span><span class="p">,</span> <span class="p">[...]);</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue components</b> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">Vue</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span> <span class="na">data</span><span class="p">:</span> <span class="kd">function</span><span class="p">(){</span> <span class="k">return</span> <span class="p">{...}</span> <span class="p">},</span> <span class="na">created</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{...},</span> <span class="na">ready</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{...},</span> <span class="na">components</span><span class="p">:</span> <span class="p">{...},</span> <span class="na">methods</span><span class="p">:</span> <span class="p">{...},</span> <span class="na">watch</span><span class="p">:</span> <span class="p">{...}</span> <span class="c1">//(other props excluded)</span> <span class="p">});</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Module notes</b><br /> Modules are a container in Angular, holding other entities such as controllers, directives, etc. In Vue they hold most component logic. </td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Vue directives</b><br /> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">myModule</span><span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="s1">'directiveName'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">injectables</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="na">restrict</span><span class="p">:</span> <span class="s1">'A'</span><span class="p">,</span> <span class="na">template</span><span class="p">:</span> <span class="s1">'&lt;div&gt;&lt;/div&gt;'</span><span class="p">,</span> <span class="na">controller</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">},</span> <span class="na">compile</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{...},</span> <span class="na">link</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span> <span class="c1">//(other props excluded)</span> <span class="p">};</span> <span class="p">});</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Angular directives</b><br /> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">Vue</span><span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="s1">'my-directive'</span><span class="p">,</span> <span class="p">{</span> <span class="na">bind</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{...},</span> <span class="na">update</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">newValue</span><span class="p">,</span> <span class="nx">oldValue</span><span class="p">)</span> <span class="p">{...},</span> <span class="na">unbind</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{...}</span> <span class="p">});</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Directive notes</b><br /> Directives are not as powerful in Vue; they seem to be more focused. In Angular a directive can be many things, better resembling a component in the Vue world. </td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Vue filters</b><br /> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">myModule</span><span class="p">.</span><span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="err">‘</span><span class="nx">filterName</span><span class="s1">', []) .filter('</span><span class="nx">reverse</span><span class="err">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="kd">function</span><span class="p">(</span><span class="nx">input</span><span class="p">)</span> <span class="p">{...};</span> <span class="p">});</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Angular filters</b><br /> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">Vue</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="s1">'reverse'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kd">function</span><span class="p">(</span><span class="nx">value</span><span class="p">){...};</span> <span class="p">});</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Filter notes</b><br /> Filters aren’t much different, although Vue provides read/write options. (see <a href="http://vuejs.org/guide/custom-filter.html" target="_blank">this guide</a>) </td> </tr> </tbody> </table> <p>Because Vue will only help us manage the VM, it won’t tackle some of the challenges that Angular covers out of the box. Services / HTTP reqs, routing, promises (just to name a few) are not among Vue’s concerns. That’s good and bad. Perhaps “bad” is an exaggeration; but it’s a negative aspect for someone that got used to Angular and didn’t have to lift a finger for it. In other words, we have to mix and match libraries as we go to cover our needs. That can be quite a task, but some developers love having this kind of control (others think it’s a waste of time). However, once you find a few good libraries, you won’t have to do it again. I think this kind of flexibility is very empowering. Imagine building your own smaller, focused framework (like Angular) on top of a good foundation: Vue.</p> <p>Creating a personalized front-end stack is great fun too. The first stack I ended up with was made out of: Director (routing), Reqwest (you guessed it), Q (promises) and of course Vue. The best name I can make out of these is QRVD (kinda sounds like curved). For the rest of the article you could have this stack in mind, although it won’t change much if you don’t.</p> <h2 id="templating">Templating</h2> <p>For the same reasons that’s easy to write templates in Angular, it’s really easy to write them in Vue. In fact, it’s a bit of a struggle to find differences between the two.</p> <p>Here’s an overview:</p> <table class="fdt-table"> <thead> <tr> <th>AngularJS</th> <th>Vue.js</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td style="width: 33.33333%;"> <b class="title">Angular interpolation</b> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="p">{{</span><span class="nx">myVariable</span><span class="p">}}</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue interpolation</b> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="p">{{</span><span class="nx">myVariable</span><span class="p">}}</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Interpolation notes</b><br /> Interpolating an object or array won't work out of the box in Vue ([Object] will be displayed). I always found that useful for debugging in Angular. Vue does come with a built-in json filter for it though: </td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Angular model binding</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">ng-model=</span><span class="s">"myVar"</span><span class="nt">&gt;</span> <span class="nt">&lt;p</span> <span class="na">ng-bind=</span><span class="s">"myVar"</span><span class="nt">&gt;&lt;/p&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue model binding</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">v-model=</span><span class="s">"myVar"</span><span class="nt">&gt;</span> <span class="nt">&lt;p</span> <span class="na">v-model=</span><span class="s">"myVar"</span><span class="nt">&gt;&lt;/p&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"></td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Angular Loops</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;li</span> <span class="na">ng-repeat=</span><span class="s">"item in items"</span> <span class="na">class=</span><span class="s">"item-{{$index}}"</span><span class="nt">&gt;</span> {{item.myProperty}} <span class="nt">&lt;/li&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue Loops</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;li</span> <span class="na">v-repeat=</span><span class="s">"items"</span> <span class="na">class=</span><span class="s">"item-{{$index}}"</span><span class="nt">&gt;</span> {{myProperty}} <span class="nt">&lt;/li&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Loop notes</b><br /> Both support model options (ex. debounce for a loop filter). BTW, we can also assign repeated objects in Vue, like in Angular:<br /> <b>v-repeat='item: items'</b> </td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Angular conditionals</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">ng-if=</span><span class="s">"myVar"</span><span class="nt">&gt;&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">ng-show=</span><span class="s">"myVar"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue conditionals</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">v-if=</span><span class="s">"myVar"</span><span class="nt">&gt;&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">v-show=</span><span class="s">"myVar"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"></td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Angular conditional classes</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">ng-class=</span><span class="s">"{‘active’: myVar}"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue conditional classes</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">v-class=</span><span class="s">"active: myVar"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Conditional classes notes</b><br /> Another example that shows similarities. <br /> Both also support <b>v-attr/ng-attr</b>. </td> </tr> <tr> <td style="width: 33.33333%;"> <b class="title">Angular event binding</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">ng-click=</span><span class="s">"myMethod($event)"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Vue event binding</b><br /> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div</span> <span class="na">v-on=</span><span class="s">"click: myMethod($event)"</span><span class="nt">&gt;&lt;/div&gt;</span></code></pre></figure> </td> <td style="width: 33.33333%;"> <b class="title">Event binding notes</b><br /> The generic <b>v-on</b> directive makes things more consistent than in Angular, but I guess it's down to personal preference. </td> </tr> </tbody> </table> <p>The list goes on. There’s even a v-cloak directive that hides the bindings before they are rendered (as ng-cloak does).</p> <p>However, there’s one directive that you won’t find in Angular: <code class="highlighter-rouge">v-with</code>. Attaching this directive and passing a data object will allow a child VM to inherit data from his parent. In my first encounter, I though of it as a drastically simplified Angular Controller, managing the data flow across the application (without the need of an equivalent to <code class="highlighter-rouge">$scope</code>. By default, components will have an isolated scope. That makes this directive essential, although components also accept <em>inherit</em> as a property (see <a href="http://vuejs.org/guide/components.html#Scope_Inheritance">more on scope inheritance</a>).</p> <h2 id="modularity--application-structures">Modularity / Application structures</h2> <p>As I mentioned, I researched this area in Angular. My conclusion was that Angular both nailed it and it didn’t. You can create great modular architectures that are indeed maintainable, reusable, comprehensible, etc. However, you’ll need a monster-build strategy to go with it, or perhaps use tools like <a href="http://requirejs.org/">Require.js</a> or <a href="http://browserify.org/">Browserify</a> to ease the pain. That’s not even the worst part. If you are just starting with Angular, you probably won’t come across a good structuring guide, which is a shame. It’s only later (or maybe too late) that Angular developers decide to look into it.</p> <p>So how does Vue.js perform? More or less the same. There’s a very short section in the <a href="http://vuejs.org/guide/application.html">Vue guide</a> about app structuring, but it’s not enough. I believe developers are able to produce kick-ass modular architectures with Vue, but there are no comprehensive guides or suggestions on how to do it. I wasn’t satisfied, so in the ‘Building larger applications’ section I tried to sketch out an example that may help developers new to Vue.</p> <h2 id="components">Components</h2> <p>Before that, I have to add a comment on components. I think, Vue managed to clearly separate components, which we can’t really say about Angular. It’s perhaps closer to Angular 2, which is great news in my mind. <br /> As a result, there’s no need for a <code class="highlighter-rouge">$scope</code> equivalent.</p> <h2 id="scopes--data-flow">Scopes / Data flow</h2> <p>I truly believe Vue is elegant when it comes to Data flow &amp; control. It felt natural and there was no need for an emit/broadcast mechanism to communicate with parent components (although Vue supports such events). There is a downside to it: the components and views become even more coupled (but hey, it is called a VM). Sometimes it’s tricky to understand which data is inherited from which <code class="highlighter-rouge">v-with</code>, so use it sparingly. There are ways to prevent spaghetti code from creeping in. Services are one good method, but there’s not much documentation about them.</p> <h2 id="services">Services</h2> <p><em>My source is written in CoffeeScript, so I’ll provide examples in both Coffee and Vanilla JS format.</em></p> <p>One approach for writing services is to use a <code class="highlighter-rouge">HTTP</code> -&gt; <code class="highlighter-rouge">Service</code> -&gt; <code class="highlighter-rouge">Component</code> mechanism. HTTP has to be generic, so we can extend it for each object:</p> <table class="fdt-table"> <thead> <tr> <th>CoffeeScript</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td style="width: 50%;"> <figure class="highlight"><pre><code class="language-coffeescript" data-lang="coffeescript"><span class="k">class</span> <span class="nx">HTTP</span> <span class="na">constructor</span><span class="o">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="p">...</span> <span class="na">get</span><span class="o">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="p">...</span> <span class="na">post</span><span class="o">:</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">...</span> <span class="na">delete</span><span class="o">:</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">...</span> <span class="na">patch</span><span class="o">:</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">...</span></code></pre></figure> </td> <td style="width: 50%;"> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">HTTP</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">HTTP</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">get</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{...};</span> <span class="nx">HTTP</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">post</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="p">{...};</span> <span class="nx">HTTP</span><span class="p">.</span><span class="nx">prototype</span><span class="p">[</span><span class="s2">"delete"</span><span class="p">]</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{...};</span> <span class="nx">HTTP</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">patch</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> <span class="p">{...};</span> <span class="k">return</span> <span class="nx">HTTP</span><span class="p">;</span> <span class="p">})();</span></code></pre></figure> </td> </tr> </tbody> </table> <p>Just for reference, a patch request using reqwest looks like this:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">reqwest</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">url</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'json'</span><span class="p">,</span> <span class="na">method</span><span class="p">:</span> <span class="s1">'patch'</span><span class="p">,</span> <span class="na">data</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">),</span> <span class="na">contentType</span><span class="p">:</span> <span class="s1">'application/json'</span> <span class="p">});</span> </code></pre> </div> <p>With CoffeeScript we can extend the HTTP class to adapt it for other use cases. For example, the User object could look like: <br /> (Remember: Angular already has the $http service, so we don’t have to do any of these bits)</p> <table class="fdt-table"> <thead> <tr> <th>CoffeeScript</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td style="width: 50%;"> <figure class="highlight"><pre><code class="language-coffeescript" data-lang="coffeescript"><span class="k">class</span> <span class="nx">UserHTTP</span> <span class="k">extends</span> <span class="nx">HTTP</span> <span class="na">all</span><span class="o">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="vi">@</span><span class="na">get</span><span class="p">(</span><span class="s">"/v1/users"</span><span class="p">)</span> <span class="na">remove</span><span class="o">:</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="vi">@</span><span class="na">delete</span><span class="p">(</span><span class="err">“</span><span class="c1">#{/v1/users/#{id}”)</span> <span class="c1">#...</span></code></pre></figure> </td> <td style="width: 50%;"> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">UserHTTP</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">superClass</span><span class="p">)</span> <span class="p">{</span> <span class="nx">extend</span><span class="p">(</span><span class="nx">UserHTTP</span><span class="p">,</span> <span class="nx">superClass</span><span class="p">);</span> <span class="kd">function</span> <span class="nx">UserHTTP</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">UserHTTP</span><span class="p">.</span><span class="nx">__super__</span><span class="p">.</span><span class="nx">constructor</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span> <span class="p">}</span> <span class="nx">UserHTTP</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">all</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">"/v1/users"</span><span class="p">);</span> <span class="p">};</span> <span class="nx">UserHTTP</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">remove</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="err">“</span><span class="o">/</span><span class="nx">v1</span><span class="o">/</span><span class="nx">users</span><span class="o">/</span><span class="err">“</span> <span class="o">+</span> <span class="nx">id</span><span class="p">);</span> <span class="p">};</span> <span class="c1">//...</span> <span class="k">return</span> <span class="nx">UserHTTP</span><span class="p">;</span> <span class="p">})(</span><span class="nx">HTTP</span><span class="p">);</span></code></pre></figure> </td> </tr> </tbody> </table> <p>Then come the actual services, which are perhaps closer to what we’re used to in Angular. For the same Object, we need to pass UserHTTP as a constructor param and then we’ll be able to call its methods and process the data as needed (before or after requests are sent).</p> <table class="fdt-table"> <thead> <tr> <th>CoffeeScript</th> <th>JavaScript</th> </tr> </thead> <tbody> <tr> <td style="width: 50%;"> <figure class="highlight"><pre><code class="language-coffeescript" data-lang="coffeescript"><span class="k">class</span> <span class="nx">UserService</span> <span class="na">constructor</span><span class="o">:</span> <span class="p">(</span><span class="vi">@</span><span class="na">HTTP</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="na">all</span><span class="o">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="vi">@</span><span class="na">HTTP</span><span class="p">.</span><span class="na">all</span><span class="p">()</span> <span class="na">remove</span><span class="o">:</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="c1"># Here we can do some data processing</span> <span class="c1"># ...</span> <span class="vi">@</span><span class="na">HTTP</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span></code></pre></figure> </td> <td style="width: 50%;"> <figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">UserService</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="kd">function</span> <span class="nx">UserService</span><span class="p">(</span><span class="nx">HTTP</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">HTTP</span> <span class="o">=</span> <span class="nx">HTTP</span><span class="p">;</span> <span class="p">}</span> <span class="nx">UserService</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">all</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{...};</span> <span class="nx">UserService</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">remove</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//Here we can do some data processing</span> <span class="c1">//...</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">HTTP</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span> <span class="p">};</span> <span class="k">return</span> <span class="nx">UserService</span><span class="p">;</span> <span class="p">})();</span></code></pre></figure> </td> </tr> </tbody> </table> <p>Finally, in any component we can instantiate a service and call the methods that in turn will make an HTTP request.</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="p">...</span> <span class="nx">HTTP</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTTP</span><span class="p">();</span> <span class="nx">userService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserService</span><span class="p">(</span><span class="nx">HTTP</span><span class="p">.</span><span class="nx">users</span><span class="p">);</span> <span class="p">...</span> <span class="c1">// Inside a Vue component</span> <span class="p">...</span> <span class="nl">ready</span><span class="p">:</span> <span class="kd">function</span><span class="p">(){</span> <span class="nx">vm</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span> <span class="nx">userService</span><span class="p">.</span><span class="nx">all</span><span class="p">()</span> <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">users</span><span class="p">){</span> <span class="nx">vm</span><span class="p">.</span><span class="nx">$set</span><span class="p">(</span><span class="s1">'users'</span><span class="p">,</span> <span class="nx">users</span><span class="p">);</span> <span class="p">});</span> <span class="p">...</span> </code></pre> </div> <p>The next section contains a diagram that illustrates how this all fits together.</p> <h2 id="building-larger-applications">Building larger applications</h2> <p>First of all, what is a large app? Well, potentially any app can become large. When it’s hard to understand how things are working and the structure is not comprehensible for outsiders, then you’re probably dealing with a large app.</p> <p>In my case, after about a week of working on a Vue application the structure was almost chaotic. It wasn’t easy to tell how it all fits together. On one hand, components weren’t properly separated, but this doesn’t have anything to do with Vue. So it was time for taking a step back, analyzing and refactoring. I sketched out my components and tried to create a structure that would make things better.</p> <p>Before you check out the diagram below, it is worth mentioning that Browserify was used to split components up. As I said before, you can either follow this approach or create a build strategy with Gulp/Grunt that concatenates the files. <br /><br /> Get a full copy of the diagram <a href="/blog/assets/getting-started-with-vuejs-angularjs-perspective/vue-large-app-structure-diagram.svg">here</a>.</p> <p><img src="/blog/assets/getting-started-with-vuejs-angularjs-perspective/vue-large-app-structure-diagram.svg" alt="Diagram: one way to structure larger Vue.js apps." /></p> <p>In terms of file/directory structure, here’s how a Vue app could look like:</p> <p>(get a full copy <a href="/blog/assets/getting-started-with-vuejs-angularjs-perspective/vue-large-app-directory-structure.svg">here</a>)</p> <p><img src="/blog/assets/getting-started-with-vuejs-angularjs-perspective/vue-large-app-directory-structure.svg" alt="Possible directory structure for a larger Vue.js app." /></p> <h2 id="final-words">Final words</h2> <p>I hope these sketches will be a good starting point/source of inspiration for devs that are starting out.</p> <p>In future posts I’ll probably write more about testing, maintainability and scalability, if I discover something interesting.</p> <p>My final words are: try out Vue, you might find it useful for many of your projects. It’s a nice little library with as many super powers as Angular!</p> How to manage multilingual website translations? 2015-05-08T12:25:54+02:00 https://fadeit.dk/blog /2015/05/08/managing-angular-translate-translations <p><strong>Update</strong>: I have written a <a href="https://fadeit.dk/blog/post/managing-translations-with-gulp-or-grunt">follow-up post</a> where I load translations during gulp/grunt build process instead of nginx proxy.</p> <h2 id="introduction">Introduction</h2> <p>This article focuses on translation management for AngularJS, however it can be applied to any translation management process. Truth is… I haven’t been a fan of the approach <a href="https://en.wikipedia.org/wiki/Gettext">gettext</a> takes, primarily because of the translation management process. I find the work-flow of having to extract translations from the code (.po file), send them to translator, compiling (.mo file) and deploying to consume significant amount of time. Key based translation like angular-translate may simplify certain aspects, but JSON files are still a limitation. They are uncomfortable to edit, prone to merge conflicts and have a strict format.</p> <h2 id="our-apporach">Our apporach</h2> <p>Fortunately there is a tool out there that offers flexible user interface, real-time editing, it fast and easy to integrate with - Google Sheets. You may know that it is possible to publish spreadsheet as HTML, however what they don’t tell you is that there is also an endpoint for returning the data in JSON format.</p> <p><img src="/blog/assets/managing-angular-translate-translations/publish.png" alt="Publishing Google Sheets" /></p> <p>Google will then give you a URL that looks like this:</p> <p><a href="https://docs.google.com/spreadsheets/d/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/pubhtml">https://docs.google.com/spreadsheets/d/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/pubhtml</a>.</p> <p>To access JSON variant, take the id of document - <code class="highlighter-rouge">1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk</code> and use it in spreadsheet feed service URL:</p> <p><a href="https://spreadsheets.google.com/feeds/list/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/od6/public/values?alt=json">https://spreadsheets.google.com/feeds/list/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/od6/public/values?alt=json</a>.</p> <p>That URL however will only display data for first page only - by default first page id in Google Docs is <code class="highlighter-rouge">od6</code>. In order to organize translations we want to store them on multiple pages (I have created Common &amp; Register pages in the screenshot above), therefore we need to get IDs of other pages using yet another feed:</p> <p><a href="https://spreadsheets.google.com/feeds/worksheets/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/private/full">https://spreadsheets.google.com/feeds/worksheets/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/private/full</a>.</p> <p>From the XML we can search for <code class="highlighter-rouge">&lt;/id&gt;</code> to extract other sheet IDs. As we see, the ID for second sheet is no longer following any pattern - <a href="https://spreadsheets.google.com/feeds/list/1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk/o14w8rc/public/values?alt=json">o14w8rc</a>.</p> <p>Even more feeds are covered on the <a href="https://developers.google.com/google-apps/spreadsheets/">Official API documentation</a>.</p> <p><img src="/blog/assets/managing-angular-translate-translations/formatted.png" alt="Formatted JSON from Google Spreadsheet feed" /></p> <p>While JSON representation is good starting point, it is a serialized representation of the document that contains a lot of meta-data. We are only interesed in the nested values, so data-structure to be transformed into what i18n framework can read. For that we are going to implement an API endpoint that will fetch translations from Google Docs, transform the structure and return translations. As seen from the screenshot above, the pattern for extracting a value is <code class="highlighter-rouge">feed-&gt;entry-&gt;gsx${language}-&gt;$t</code>. The minimal example below utilizes <a href="http://flask.pocoo.org/">Flask microframework</a> written in Python</p> <div class="language-python highlighter-rouge"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">json</span> <span class="kn">import</span> <span class="nn">urllib</span> <span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span><span class="p">,</span> <span class="n">abort</span> <span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">OrderedDict</span> <span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span> <span class="nd">@app.route</span><span class="p">(</span><span class="s">'/translation/&lt;lang&gt;/&lt;filename&gt;'</span><span class="p">)</span> <span class="k">def</span> <span class="nf">fetch_translation</span><span class="p">(</span><span class="n">lang</span><span class="p">,</span> <span class="n">filename</span><span class="p">):</span> <span class="c"># Mapping Google Docs page ID to the translation file</span> <span class="n">page_map</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'common.json'</span><span class="p">:</span> <span class="s">'od6'</span><span class="p">,</span> <span class="s">'register.json'</span><span class="p">:</span> <span class="s">'o14w8rc'</span> <span class="p">}</span> <span class="n">sheet_id</span> <span class="o">=</span> <span class="s">'1FsVuRLbtgxMZvWd4mpnKiAhqVYap-ZAx08LBeZ9HFJk'</span> <span class="n">base</span> <span class="o">=</span> <span class="s">'https://spreadsheets.google.com/feeds/list/{0}/{1}/public/values?alt=json'</span> <span class="k">if</span> <span class="n">filename</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">page_map</span><span class="p">:</span> <span class="n">abort</span><span class="p">(</span><span class="mi">404</span><span class="p">)</span> <span class="n">page_id</span> <span class="o">=</span> <span class="n">page_map</span><span class="p">[</span><span class="n">filename</span><span class="p">]</span> <span class="n">url</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">sheet_id</span><span class="p">,</span> <span class="n">page_id</span><span class="p">)</span> <span class="n">json_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf8'</span><span class="p">))</span> <span class="c"># We need to build different data structure</span> <span class="n">translation_map</span> <span class="o">=</span> <span class="n">OrderedDict</span><span class="p">()</span> <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">json_data</span><span class="p">[</span><span class="s">'feed'</span><span class="p">][</span><span class="s">'entry'</span><span class="p">]:</span> <span class="n">key</span> <span class="o">=</span> <span class="n">entry</span><span class="p">[</span><span class="s">'gsx$key'</span><span class="p">][</span><span class="s">'$t'</span><span class="p">]</span> <span class="n">value</span> <span class="o">=</span> <span class="n">entry</span><span class="p">[</span><span class="s">'gsx${0}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">lang</span><span class="p">)][</span><span class="s">'$t'</span><span class="p">]</span> <span class="k">if</span> <span class="n">value</span><span class="p">:</span> <span class="n">translation_map</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">translation_map</span><span class="p">)</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span> <span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">()</span> </code></pre> </div> <p>Now let’s run the server and fetch translations at http://127.0.0.1:5000/translation/en-us/register.json</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>curl http://127.0.0.1:5000/translation/en-us/register.json <span class="o">{</span><span class="s2">"WELCOME"</span>: <span class="s2">"Welcome"</span><span class="o">}</span> </code></pre> </div> <p>Fetching translations is convenient while developing and translating, however we wouldn’t want production server to fetch translations from Google Docs every time it gets a request. Easiest solution would be to cache translations for certain amount of time, we also want to version control translations, therefore the strategy is a little different. Production server would be serving static JSON files from the disc, whereas on development instance NGINX will intercept any requests going to /translation and proxy them to the application server instead:</p> <p><img src="/blog/assets/managing-angular-translate-translations/flow.png" alt="Translation loading flow" /></p> <p>For development NGINX configuration we need to add a location block like so:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code><span class="c">#proxy translations from Google Docs on development</span> location /translation <span class="o">{</span> proxy_pass http://127.0.0.1:5000<span class="nv">$uri</span>; proxy_redirect off; proxy_set_header Host <span class="nv">$host</span>; proxy_set_header X-Real-IP <span class="nv">$remote_addr</span>; proxy_set_header X-Forwarded-For <span class="nv">$proxy_add_x_forwarded_for</span>; <span class="o">}</span> </code></pre> </div> <h2 id="conclusion">Conclusion</h2> <p>All translations now come from one consistent source, no more mismatches and conflicts.</p> <p>With Git we get:</p> <ul> <li>Accidental modifications in Google Docs don’t automatically end up in production</li> <li>Each translation has a revision history&lt;/li&gt;&lt;li&gt;Finding differences between edits is simple</li> <li>Blame view helps finding wrongdoer</li> </ul> <p>Google Docs brings to the table:</p> <ul> <li>Spotting missing translations is easy because all languages are visible on same sheet (.json files are separate for each language)</li> <li>Access and control management of editors is simple</li> <li>Multiple people can edit same document in realtime</li> <li>Changes to translations are visible immediately (on development setup)</li> <li>Sheets supports highlighting and commenting</li> </ul> <p>Dependencies needed to run the flask server:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code><span class="c">#Install dependencies</span> sudo apt-get install virtualenv python3.4-dev python-pip <span class="c">#Create virtual environment</span> virtualenv -p /usr/bin/python3.4 venv <span class="c">#Activate virtual environment</span> <span class="nb">source </span>venv/bin/activate <span class="c">#Install libraries</span> pip install flask <span class="c">#Run server</span> python server.py <span class="c">#Fetch translations</span> http://localhost:5000/translation/en-us/register.json </code></pre> </div> Pre-loading partial loader translations 2015-04-09T16:20:16+02:00 https://fadeit.dk/blog /2015/04/09/preload-angular-translate-partial-loader <h2 id="introduction">Introduction</h2> <p><a href="https://angular-translate.github.io/">Angular-translate</a> is the de-facto internationalisation module for AngularJS applications. Basically it works by replacing translation keys from the translation dictionary. The problem is that larger applications have a lot of content, thus a lot of translations so it doesn’t make sense to send the entire translations file to the client on initial page load. The good news is that there is a solution - <a href="https://github.com/angular-translate/angular-translate/blob/master/src/service/loader-partial.js">partial loader</a>. It works as advertised - we can define translation files that are required to load for that page, thus lowering the amount of data required to transfer. The downside to partial loading is that first browser must initialize angular, which in turn will start loading the translation partials. The issue becomes apparent on slower connections where translation keys are either shown or invisible until partial loader finishes fetching the files. It can be a factor when trying to make a first impression. What if we could pre-load some parts that are common throughout our application? It would help improve the user experience on most pages, but will not eliminate the use of partial translations.</p> <p>Behind the scenes the partial loader uses angular’s <code class="highlighter-rouge">$http</code> service to fetch a JSON file and store it in an array. Fortunately for us, angular has a built-in caching mechanism for <code class="highlighter-rouge">$http</code> service that we could leverage for our purpose. We are going to store the parts we want to pre-load in the $http cache, so when partial loader tries to fetch the files, it will hit the cache.<br />By default the caching mechanism for <code class="highlighter-rouge">$http</code> service is using <code class="highlighter-rouge">$cacheFactory</code>’s cache with <code class="highlighter-rouge">'$http'</code> key, although caching mechanism can be overridden. Cache key is the request URL and value is an array containing 4 items:</p> <ul> <li>response status - 200 for success</li> <li>response headers - we leave it blank</li> <li>data - JSON for our needs</li> <li>status text - ‘OK’ for fulfilled request</li> </ul> <p>Assuming the translation file we want to pre-load is available at http://example.com/assets/translations/da-dk/common.json, let’s test out the concept by creating a function that fakes cache entry for us:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s1">'app'</span><span class="p">,</span> <span class="p">[</span><span class="s1">'pascalprecht.translate'</span><span class="p">]);</span> <span class="nx">app</span><span class="p">.</span><span class="nx">config</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">$translateProvider</span><span class="p">)</span> <span class="p">{</span> <span class="nx">$translateProvider</span><span class="p">.</span><span class="nx">useLoader</span><span class="p">(</span><span class="s1">'$translatePartialLoader'</span><span class="p">,</span> <span class="p">{</span> <span class="na">urlTemplate</span><span class="p">:</span> <span class="s1">'/assets/translations/{lang}/{part}.json'</span><span class="p">,</span> <span class="na">$http</span><span class="p">:</span> <span class="p">{</span> <span class="na">cache</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="c1">//Enable caching for PartialLoader</span> <span class="p">});</span> <span class="p">});</span> <span class="nx">app</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="kd">function</span> <span class="nx">run</span> <span class="p">(</span><span class="nx">$cacheFactory</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">httpCache</span> <span class="o">=</span> <span class="nx">$cacheFactory</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'$http'</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">putHttpCache</span> <span class="o">=</span> <span class="kd">function</span> <span class="nx">putHttpCache</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">,</span> <span class="nx">cacheValue</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">cacheObj</span> <span class="o">=</span> <span class="p">[</span><span class="mi">200</span><span class="p">,</span> <span class="nx">cacheValue</span><span class="p">,</span> <span class="p">{},</span> <span class="s2">"OK"</span><span class="p">];</span> <span class="nx">httpCache</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">,</span> <span class="nx">cacheObj</span><span class="p">);</span> <span class="p">};</span> <span class="kd">var</span> <span class="nx">cacheValue</span> <span class="o">=</span> <span class="nx">angular</span><span class="p">.</span><span class="nx">toJson</span><span class="p">({</span><span class="na">TEST_KEY</span><span class="p">:</span> <span class="s2">"Cached key test"</span><span class="p">});</span> <span class="nx">putHttpCache</span><span class="p">(</span><span class="s1">'/assets/translations/da-dk/common.json'</span><span class="p">,</span> <span class="nx">cacheValue</span><span class="p">);</span> <span class="p">});</span> </code></pre> </div> <p>It works, however we have translation keys in code. Now we need to come up with a way to ship the desired JSON files at initial application load. An approach would be to keep translations in a .js file instead (an angular service, returning JSON string on call), however that would require us to migrate existing JSON files into Javascript objects and reduce translation maintainability. But wait, there is a Grunt plugin that already stores HTML templates in a similar way (<a href="https://github.com/karlgoldstein/grunt-html2js">html2js</a>). The only difference is that the plugin uses <code class="highlighter-rouge">$templateCache</code> instead. Since <code class="highlighter-rouge">html2js</code> is part of our build process already, what we can do is pass the JSON translations to the angular app using the existing Grunt build task. Once angular is bootstrapped, we’ll load translations from <code class="highlighter-rouge">$templateCache</code> and store them into the <code class="highlighter-rouge">$cacheFactory</code> cache as well. Here’s how the Grunt task for storing .json files in <code class="highlighter-rouge">$templateCache</code> looks like:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">html2js</span><span class="err">:</span> <span class="p">{</span> <span class="c1">//...template caching task omitted...</span> <span class="nl">translations</span><span class="p">:</span> <span class="p">{</span> <span class="nl">options</span><span class="p">:</span> <span class="p">{</span> <span class="nl">htmlmin</span><span class="p">:</span> <span class="p">{},</span> <span class="c1">//override minification settings</span> <span class="nx">base</span><span class="err">:</span> <span class="s1">'src'</span> <span class="p">},</span> <span class="nx">src</span><span class="err">:</span> <span class="p">[</span> <span class="s1">'src/assets/translations/**/*.json'</span> <span class="p">],</span> <span class="nx">dest</span><span class="err">:</span> <span class="s1">'&lt;%= build_dir %&gt;/templates-translations.js'</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Let’s now ‘teach’ the <code class="highlighter-rouge">putHttpCache</code> method to take translations from <code class="highlighter-rouge">$templateCache</code>:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">app</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="kd">function</span> <span class="nx">run</span> <span class="p">(</span><span class="nx">$cacheFactory</span><span class="p">,</span> <span class="nx">$templateCache</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">httpCache</span> <span class="o">=</span> <span class="nx">$cacheFactory</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'$http'</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">putHttpCache</span> <span class="o">=</span> <span class="kd">function</span> <span class="nx">putHttpCache</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">cacheObj</span> <span class="o">=</span> <span class="p">[</span><span class="mi">200</span><span class="p">,</span> <span class="nx">$templateCache</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">),</span> <span class="p">{},</span> <span class="s2">"OK"</span><span class="p">];</span> <span class="nx">httpCache</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="s1">'/'</span> <span class="o">+</span> <span class="nx">cacheKey</span><span class="p">,</span> <span class="nx">cacheObj</span><span class="p">);</span> <span class="c1">//prepend '/' to make url's match</span> <span class="p">};</span> <span class="nx">putHttpCache</span><span class="p">(</span><span class="s1">'assets/translations/da-dk/common.json'</span><span class="p">);</span> <span class="p">});</span> </code></pre> </div> <h2 id="conclusion">Conclusion</h2> <p>Now we can take the best of both worlds - preload most frequently used translations and lazy-load translations for less visited pages. Our html2js build process works in similar manner - all templates ending with <code class="highlighter-rouge">*.tpl2js.html</code> are stored in <code class="highlighter-rouge">$templateCache</code>, whereas templates ending with <code class="highlighter-rouge">*.tpl.html</code> are only fetched on demand.</p> AngularJS SEO friendly translation URLs with ui-router 2015-03-30T18:10:55+02:00 https://fadeit.dk/blog /2015/03/30/angular-translate-ui-router-seo <h2 id="introduction">Introduction</h2> <p>In a <a href="https://fadeit.dk/blog/post/angularjs-seo-for-angular-translate">previous</a> post we explored how to introduce unique URLs per language using primarily NGINX and Grunt configuration. To recap, that approach is reasonable if you already have a working application and prefer not to re-factor every state and view that links to it. While it may be easier to implement in an existing application, it forces users to perform a refresh when switching between languages - though not a big issue (how often do users switch back-and-forth anyway), having a Single Page Application (SPA) that forces users to reload pages is sub-optimal. In this article we will explore possibilities of implementing SEO friendly URLs using ui-router.</p> <p>Nested states is the key to introducing language as a URL variable to all states. We start off by creating the ‘app’ state that every other state will be child of. The app state is abstract, so that the state by itself can not be activated. Next we want the route to only match the languages we intend to support (in this example ‘da’ and ‘en’) to reduce possible false-positive matches e.g example.com/<b>da</b>ta. As ui-router injects the content of the child states into ui-view, we define it as an inline template for the parent route. We also define ‘app.home’ state that will handle example.com/en and example.com/da pages:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">$stateProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">(</span><span class="s1">'app'</span><span class="p">,</span> <span class="p">{</span> <span class="na">abstract</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="s1">'/{lang:(?:da|en)}'</span><span class="p">,</span> <span class="na">template</span><span class="p">:</span> <span class="s1">'&lt;ui-view/&gt;'</span> <span class="p">});</span> <span class="nx">$stateProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">(</span><span class="s1">'app.home'</span><span class="p">,</span> <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="na">templateUrl</span><span class="p">:</span> <span class="s1">'views/home-page.html'</span><span class="p">,</span> <span class="p">});</span> </code></pre> </div> <p>Now every state that we want to translate should be a child of app state:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">$stateProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">(</span><span class="s1">'app.about'</span><span class="p">,</span> <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="s1">'/about'</span><span class="p">,</span> <span class="na">templateUrl</span><span class="p">:</span> <span class="s1">'views/about-page.html'</span><span class="p">,</span> <span class="p">});</span> </code></pre> </div> <p>Since the child states contain a language path variable, we need to alter the links pointing to the child states to pass in <code class="highlighter-rouge">lang</code> parameter. But before we do so, we need to set <code class="highlighter-rouge">activeLang</code> on <code class="highlighter-rouge">$rootScope</code> so we can read it from there when generating links with <code class="highlighter-rouge">ui-sref</code>. Due to the fact that <code class="highlighter-rouge">activeLang</code> can change at state change, we need to subscribe to <code class="highlighter-rouge">stateChangeSuccess</code> event. While at it, we can also generate links for the same page in other languages - ‘In English’ link at example.com/da/about should point to example.com/en/about. In this simple example we use only two languages, so we’ll be using only one variable <code class="highlighter-rouge">otherLangURL</code>:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">navigationController</span><span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$rootScope</span><span class="p">,</span> <span class="nx">$stateParams</span><span class="p">,</span> <span class="nx">$translate</span><span class="p">){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">'$stateChangeSuccess'</span><span class="p">,</span> <span class="kd">function</span> <span class="nx">rootStateChangeSuccess</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">toState</span><span class="p">){</span> <span class="k">if</span><span class="p">(</span><span class="nx">$stateParams</span><span class="p">.</span><span class="nx">lang</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">){</span> <span class="kd">var</span> <span class="nx">otherLang</span> <span class="o">=</span> <span class="nx">$stateParams</span><span class="p">.</span><span class="nx">lang</span> <span class="o">===</span> <span class="s1">'da'</span> <span class="p">?</span> <span class="s1">'en'</span> <span class="p">:</span> <span class="s1">'da'</span><span class="p">;</span> <span class="nx">$rootScope</span><span class="p">.</span><span class="nx">activeLang</span> <span class="o">=</span> <span class="nx">$stateParams</span><span class="p">.</span><span class="nx">lang</span><span class="p">;</span> <span class="nx">$rootScope</span><span class="p">.</span><span class="nx">otherLangURL</span> <span class="o">=</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">absUrl</span><span class="p">().</span><span class="nx">replace</span><span class="p">(</span><span class="s1">'/'</span> <span class="o">+</span> <span class="nx">$stateParams</span><span class="p">.</span><span class="nx">lang</span><span class="p">,</span> <span class="s1">'/'</span> <span class="o">+</span><span class="nx">otherLang</span><span class="p">);</span> <span class="nx">$translate</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">$stateParams</span><span class="p">.</span><span class="nx">lang</span><span class="p">);</span> <span class="p">}</span> <span class="p">});</span> <span class="p">}</span> </code></pre> </div> <p>It would be nice to use <code class="highlighter-rouge">ui-sref</code> to dynamically generate the links to same page in other languages, but unfortunately <code class="highlighter-rouge">ui-sref</code> does not support variables in state names, therefore we have to fall back to <code class="highlighter-rouge">ng-href</code> for generating the links. Fortunately path variable substitution is supported, therefore we can generate menu links using <code class="highlighter-rouge">activeLang</code> variable we stored on <code class="highlighter-rouge">$rootScope</code>:</p> <div class="language-html highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;ul</span> <span class="na">class=</span><span class="s">"menu-navigation"</span><span class="nt">&gt;</span> ... <span class="nt">&lt;li</span> <span class="na">ui-sref-active=</span><span class="s">"activeMenu"</span><span class="nt">&gt;</span> <span class="nt">&lt;a</span> <span class="na">href</span> <span class="na">ui-sref=</span><span class="s">"app.about({lang: activeLang})"</span> <span class="na">translate</span><span class="nt">&gt;</span> NAV_ABOUT <span class="nt">&lt;/a&gt;</span> <span class="nt">&lt;/li&gt;</span> <span class="nt">&lt;li&gt;</span> <span class="nt">&lt;a</span> <span class="na">ng-href=</span><span class="s">""</span> <span class="na">translate</span><span class="nt">&gt;</span> IN_LANGUAGE <span class="nt">&lt;/a&gt;</span> <span class="nt">&lt;/li&gt;</span> <span class="nt">&lt;/ul&gt;</span> </code></pre> </div> <h2 id="conclusion">Conclusion</h2> <p>Incorporating language in the application routes from very start will save time in the long run as re-factoring hundreds of <code class="highlighter-rouge">ui-sref</code> links will take it’s time. Ui-router fully supports nested sub-states, so there will not be any conflict if your application is already using nested states, just make sure that there is <code class="highlighter-rouge">ui-view</code> for each parent state to inject the content into. I was unable to make the lang parameter in the parent state optional, thus language will be enforced in every path (as opposed to <a href="https://fadeit.dk/blog/post/angularjs-seo-for-angular-translate">previous post</a> where we were able to use paths without language defined in them). Have a better approach? leave a comment!</p> AngularJS SEO friendly translations 2015-03-12T16:30:16+01:00 https://fadeit.dk/blog /2015/03/12/angularjs-seo-for-angular-translate <p><strong>Update</strong>: I have written a follow-up post solving the same problem using ui-router <a href="https://fadeit.dk/blog/post/angular-translate-ui-router-seo">here</a></p> <h2 id="introduction">Introduction</h2> <p>Instant translations, this is what <a href="http://angular-translate.github.io/">angular-translate</a> brings to the table. While it is significantly more convenient than waiting one more site load cycle for a different language to load (especially on sites that are slow to begin with), it doesn’t come without complications. If each translation language does not have a unique URL, search engines can not link search results to it, let alone crawl the site properly.</p> <p>Here’s what Google has to say on the matter:</p> <blockquote> <p><strong>Make sure each language version is easily discoverable</strong> <em>Keep the content for each language on separate URL’s. Don’t use cookies to show translated versions of the page. Consider cross-linking each language version of a page.</em> <a href="https://support.google.com/webmasters/answer/182192">https://support.google.com/webmasters/answer/182192</a></p> </blockquote> <h2 id="options">Options</h2> <p>Okay, perhaps it’s a nice practice to have language in the URL. Here are the options:</p> <ol> <li>Store the language in a GET parameter e.g: <strong>example.com/about?lang=en</strong></li> <li>Store the language in a path e.g: <strong>example.com/en/about</strong></li> <li>Store the language in a sub-domain e.g: <strong>en.example.com/about</strong></li> </ol> <p>While the first option would be the easiest to implement, it is also the ugliest. The other two are relatively similar in terms of appearance and it boils down to your personal preference. An important distincion to make is that if language is stored in a sub-domain, it excludes the possibility of handling the routing with angular as subdomain is out of application router scope. We decided to explore the second option - having the language in a URL path.</p> <p>The preferred approach would be to introduce a path variable on a router base path and have all children be relative to it or escape the language before routing is performed. While it is possible, it seems that the only way to achieve it is to nest all states in the following manner:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">angular</span><span class="p">.</span><span class="nx">module</span><span class="p">(</span><span class="s1">'app'</span><span class="p">).</span><span class="nx">config</span><span class="p">([</span> <span class="s1">'$stateProvider'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">$stateProvider</span><span class="p">)</span> <span class="p">{</span> <span class="nx">$stateProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">(</span><span class="s1">'app'</span><span class="p">,</span> <span class="p">{</span> <span class="na">abstract</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">url</span><span class="p">:</span> <span class="s1">'/{lang:(?:da|en)}'</span><span class="p">,</span> <span class="na">template</span><span class="p">:</span> <span class="s1">'&lt;ui-view/&gt;'</span><span class="p">,</span> <span class="na">controller</span><span class="p">:</span> <span class="s1">'RootController'</span> <span class="p">});</span> <span class="nx">$stateProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">(</span><span class="s1">'app.root'</span><span class="p">,</span> <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="na">templateUrl</span><span class="p">:</span> <span class="s1">'views/home.html'</span><span class="p">,</span> <span class="p">});</span> <span class="nx">$stateProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">(</span><span class="s1">'app.about'</span><span class="p">,</span> <span class="p">{</span> <span class="na">url</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="na">templateUrl</span><span class="p">:</span> <span class="s1">'views/about.html'</span><span class="p">,</span> <span class="p">});</span> <span class="p">}</span> <span class="p">]);</span> </code></pre> </div> <p>It is likely that by the time SEO becomes a priority, the application is already built and refactoring code does not sound appealing. To name a few, using ui-router has following downsides:</p> <ul> <li>Language slug becomes mandatory for each state, so example.com/about would become example.com/da/about</li> <li>All child state names need to be changed</li> <li>All links have to be refactored</li> </ul> <p>We’ll be taking a closer look at ui-router in a separate post soon, while this article focuses on finding an alternative configuration-based approach.</p> <h2 id="automation-to-the-rescue">Automation to the rescue!</h2> <p>Hosting Angular application is just serving static files, where each URL maps to a directory/file by default. Therefore, if we are to host the same application in a different directory for each language, we get SEO friendly URLs without a major overhaul of the code. If you are working on a big Angular project, the chances are that either Grunt or Gulp is in the mix. Grunt is our weapon of choice, so we need to add an extra copy task at the end of the build process that would copy all files into a subdirectory:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">copy</span><span class="err">:</span> <span class="p">{</span> <span class="nl">build_en_lang</span><span class="p">:</span> <span class="p">{</span> <span class="nl">files</span><span class="p">:</span> <span class="p">[</span> <span class="p">{</span> <span class="na">src</span><span class="p">:</span> <span class="p">[</span> <span class="s1">'**'</span><span class="p">,</span> <span class="s1">'!en/**'</span><span class="p">],</span> <span class="na">dest</span><span class="p">:</span> <span class="s1">'&lt;%= build_dir %&gt;/en'</span><span class="p">,</span> <span class="na">cwd</span><span class="p">:</span> <span class="s1">'&lt;%= build_dir %&gt;'</span><span class="p">,</span> <span class="na">expand</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">]</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p><code class="highlighter-rouge">!en/**</code> tells Grunt not to copy itself every time that task is called.</p> <p>The directory structure should be following:</p> <ul> <li>/path/to/app/index.html (will default to Danish)</li> <li>/path/to/app/other/files</li> <li>/path/to/app/en/index.html</li> <li>/path/to/app/en/other/files</li> </ul> <p>Now we need to tell Angular to check for URL when determining language:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">absUrl</span> <span class="o">=</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">absUrl</span><span class="p">();</span> <span class="k">if</span><span class="p">(</span><span class="nx">absUrl</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'/en/'</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">activeLang</span> <span class="o">=</span> <span class="s1">'en-us'</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span><span class="p">{</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">activeLang</span> <span class="o">=</span> <span class="s1">'da-dk'</span><span class="p">;</span> <span class="p">}</span> <span class="nx">$translate</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">$scope</span><span class="p">.</span><span class="nx">activeLang</span><span class="p">);</span> </code></pre> </div> <p><code class="highlighter-rouge">activeLang</code> is stored on scope because it will be later used to determine the class of language switching button</p> <p>Google also suggests cross-linking each language version of the page. If user navigates to a different page, we should also update the link that points to the same page in other languages. For that we use $stateChangeSuccess event:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="c1">//Construct url base</span> <span class="kd">var</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">port</span><span class="p">();</span> <span class="c1">//Port can be omitted on 80 or 443</span> <span class="nx">port</span> <span class="o">=</span> <span class="p">(</span><span class="nx">port</span> <span class="o">===</span> <span class="mi">80</span> <span class="o">||</span> <span class="nx">port</span> <span class="o">===</span> <span class="mi">443</span><span class="p">)</span> <span class="p">?</span> <span class="s1">''</span> <span class="p">:</span> <span class="s1">':'</span> <span class="o">+</span> <span class="nx">port</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">urlBase</span> <span class="o">=</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">protocol</span><span class="p">()</span> <span class="o">+</span> <span class="s1">'://'</span> <span class="o">+</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">host</span><span class="p">()</span> <span class="o">+</span> <span class="nx">port</span><span class="p">;</span> <span class="nx">$rootScope</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s1">'$stateChangeSuccess'</span><span class="p">,</span><span class="kd">function</span><span class="p">(){</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">enUrl</span> <span class="o">=</span> <span class="nx">urlBase</span> <span class="o">+</span> <span class="s1">'/en'</span> <span class="o">+</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">url</span><span class="p">();</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">daUrl</span> <span class="o">=</span> <span class="nx">urlBase</span> <span class="o">+</span> <span class="nx">$location</span><span class="p">.</span><span class="nx">url</span><span class="p">();</span> <span class="p">});</span> </code></pre> </div> <p>The URLs need to be absolute, otherwise if this code was executed at example.com/en, it would calculate the ‘en’ URL to be at example.com/en/en and so on.</p> <p>Next we need to display our cross-links in the template using <code class="highlighter-rouge">ng-href</code> directive:</p> <div class="language-html highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">ng-href=</span><span class="s">""</span> <span class="na">target=</span><span class="s">"_self"</span> <span class="na">ng-class=</span><span class="s">"{'active': activeLang === 'en-us'}"</span><span class="nt">&gt;</span> <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"assets/images/gb.svg"</span> <span class="na">alt=</span><span class="s">"Change language to English"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/a&gt;</span> <span class="nt">&lt;a</span> <span class="na">ng-href=</span><span class="s">""</span> <span class="na">target=</span><span class="s">"_self"</span> <span class="na">ng-class=</span><span class="s">"{'active': activeLang === 'da-dk'}"</span><span class="nt">&gt;</span> <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"assets/images/dk.svg"</span> <span class="na">alt=</span><span class="s">"Change language to Danish"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/a&gt;</span> </code></pre> </div> <p><code class="highlighter-rouge">target='_self'</code> is used to tell Angular not to load this link in ajax but to navigate to it the Angular way. We need the browser to fetch appropriate index.html page and bootstrap the application (since we have one application per language).</p> <p>The application is now SEO friendly, but we are not quite done yet. We are also using HTML5 mode to have pretty URLs. When using HTML5 mode, we must define base tag in the index file so Angular knows where the application path starts from:</p> <div class="language-html highlighter-rouge"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span> <span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"da"</span> <span class="na">ng-app=</span><span class="s">"example"</span> <span class="na">ng-controller=</span><span class="s">"AppCtrl"</span><span class="nt">&gt;</span> <span class="nt">&lt;head&gt;</span> <span class="nt">&lt;base</span> <span class="na">href=</span><span class="s">"/"</span><span class="nt">&gt;</span> <span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"alternate"</span> <span class="na">hreflang=</span><span class="s">"en"</span> <span class="na">href=</span><span class="s">"http://example.com/en"</span><span class="nt">/&gt;</span> <span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"alternate"</span> <span class="na">hreflang=</span><span class="s">"da"</span> <span class="na">href=</span><span class="s">"http://example.com"</span><span class="nt">/&gt;</span> ... </code></pre> </div> <p>In case <code class="highlighter-rouge">hreflang</code> caught your eye, you can read more about it <a href="https://support.google.com/webmasters/answer/182192">here</a>.</p> <p>However, now we also have a copy of the site running at /en, so the base tag needs to be appropriate.</p> <h2 id="grunt-to-the-rescue-again">Grunt to the rescue, again!</h2> <p>We need to update the base tag when making a copy of the site to match the path. Grunt’s copy task features a process option that is called for each file copied. The process option enables us to transform the contents of the file:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">copy</span><span class="err">:</span> <span class="p">{</span> <span class="nl">options</span><span class="p">:</span> <span class="p">{</span> <span class="nl">processContent</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">contents</span><span class="p">,</span> <span class="nx">srcpath</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span><span class="p">(</span><span class="nx">srcpath</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'index.html'</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">){</span> <span class="c1">//note spaces around in order not to match 'hreflang' tag</span> <span class="nx">contents</span> <span class="o">=</span> <span class="nx">contents</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">' lang="da" '</span><span class="p">,</span> <span class="s1">' lang="en" '</span><span class="p">);</span> <span class="nx">contents</span> <span class="o">=</span> <span class="nx">contents</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">'&lt;base href="/"&gt;'</span><span class="p">,</span> <span class="s1">'&lt;base href="/en/"&gt;'</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">contents</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">//build_en_lang omitted</span> <span class="p">}</span> </code></pre> </div> <p>We are using older version of Grunt, so the option is called <code class="highlighter-rouge">processContent</code> instead of <code class="highlighter-rouge">process</code>.</p> <p>HTML5 mode also requires URL rewriting on the server side. If a request is made at example.com/about, the server should still return the index.html page and it is up to Angular router to figure out what to do with the ‘about’ path variable. In Nginx, this is typically achieved with try_files directive:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>location / <span class="o">{</span> try_files <span class="nv">$uri</span> <span class="nv">$uri</span>/ <span class="nv">$uri</span>/index.html /index.html; <span class="o">}</span> </code></pre> </div> <p>If a request is made at example.com/en/about, the configuration above will result in Nginx trying following files:</p> <ul> <li>en/about –&gt; not found</li> <li>en/about/ –&gt; not found</li> <li>en/about/index.html –&gt; not found</li> <li>index.html –&gt; found</li> </ul> <p>This results in the server returning the index.html file of the root rather than of ‘en’ directory. Now the router is trying to figure out which state to match the <code class="highlighter-rouge">en/about</code> path to, which it will of course fail to do accomplish. We need to define another location to load proper index.html, if $uri is not a match:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>location /en/ <span class="o">{</span> try_files <span class="nv">$uri</span> <span class="nv">$uri</span>/ <span class="nv">$uri</span>/index.html /en/index.html; <span class="o">}</span> </code></pre> </div> <p>Finally, SEO friendly multilingual application!<br />Now that we’ve established that there is no way around configuring web server, let’s make most of it! Since the only file that actually changed is index.html, there is no real need to copy other resources. We start out by changing the grunt copy task to only copy index file to same directory and rename it to index.en.html:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">copy</span><span class="p">{</span> <span class="nl">build_en_lang</span><span class="p">:</span> <span class="p">{</span> <span class="nl">options</span><span class="p">:</span> <span class="p">{</span> <span class="nl">processContent</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">contents</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//note spaces around in order not to match 'hreflang' tag</span> <span class="nx">contents</span> <span class="o">=</span> <span class="nx">contents</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">' lang="da" '</span><span class="p">,</span> <span class="s1">' lang="en" '</span><span class="p">);</span> <span class="nx">contents</span> <span class="o">=</span> <span class="nx">contents</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">'&lt;base href="/"&gt;'</span><span class="p">,</span> <span class="s1">'&lt;base href="/en/"&gt;'</span><span class="p">);</span> <span class="k">return</span> <span class="nx">contents</span><span class="p">;</span> <span class="p">},</span> <span class="p">},</span> <span class="nx">files</span><span class="err">:</span> <span class="p">[</span> <span class="p">{</span> <span class="na">src</span><span class="p">:</span> <span class="p">[</span> <span class="s1">'index.html'</span><span class="p">],</span> <span class="na">dest</span><span class="p">:</span> <span class="s1">'&lt;%= build_dir %&gt;/'</span><span class="p">,</span> <span class="na">cwd</span><span class="p">:</span> <span class="s1">'&lt;%= build_dir %&gt;'</span><span class="p">,</span> <span class="na">expand</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">rename</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">dest</span><span class="p">,</span> <span class="nx">src</span><span class="p">){</span> <span class="k">return</span> <span class="nx">dest</span> <span class="o">+</span> <span class="nx">src</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">'.html'</span><span class="p">,</span> <span class="s1">'.en.html'</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">]</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Now Nginx ‘en’ location needs to serve index.en.html instead of index.html, otherwise it should ignore /en/ part of the URL when serving static files. We will use regular expression to match /en and capture everything that comes after into $1. Then we use captured group in try_files directive:</p> <div class="language-bash highlighter-rouge"><pre class="highlight"><code>location ~ ^/en/?<span class="o">(</span>.<span class="k">*</span><span class="o">)</span><span class="nv">$ </span><span class="o">{</span> index index.en.html; try_files /<span class="nv">$1</span> /<span class="nv">$1</span>/ /index.en.html; <span class="o">}</span> </code></pre> </div> <p>We have jumped through several hoops in order to get rid of the comfort angular-translate provides. Fortunately we can bring some of it back - authenticated users are not crawlers, so we can display ‘quick buttons’ for them while displaying SEO friendly cross-links to unauthenticated users:</p> <div class="language-html highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">ng-if=</span><span class="s">"!auth.isAuthenticated"</span><span class="nt">&gt;</span> <span class="nt">&lt;a</span> <span class="na">ng-href=</span><span class="s">""</span> <span class="na">target=</span><span class="s">"_self"</span> <span class="na">ng-class=</span><span class="s">"{'active': activeLang === 'en-us'}"</span><span class="nt">&gt;</span> <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"assets/images/gb.svg"</span> <span class="na">alt=</span><span class="s">"Change language to English"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/a&gt;</span> ... <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">ng-if=</span><span class="s">"auth.isAuthenticated"</span><span class="nt">&gt;</span> <span class="nt">&lt;a</span> <span class="na">ng-href=</span><span class="s">"#"</span> <span class="na">ng-click=</span><span class="s">"changeLang('en-us')"</span> <span class="na">ng-class=</span><span class="s">"{'active': activeLang === 'en-us'}"</span><span class="nt">&gt;</span> <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"assets/images/gb.svg"</span> <span class="na">alt=</span><span class="s">"Change language to English"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/a&gt;</span> ... <span class="nt">&lt;/div&gt;</span> </code></pre> </div> <h2 id="conclusion">Conclusion</h2> <p>With this approach we have tried to solve as much as possible with configuration, so we’ve managed not to entangle language awareness with every state in the application. While ui-router configuration would be preferred, it does not come without limitations: ui-router can not be used for sub-domains and ui-router enforces using language in every url rather than having it as optional parameter.</p> <p>At the end of day there is no good way to to play by SEO rules, so hopefully AngularJS 2.0 will change this.</p>