David Chang


The development of Ziltag, an open source image annotation tool

Hello Friends!

Ziltag is an open source image annotation tool that allows you to tag and comment directly on any web images. Ziltag is an easy to use web plugin to discuss, collaborate, showcase, and explore on images and it consists of an API server, an UI server, and a JavaScript plugin ( Github | Deploy Instruction ).

If you’re running a blog, it can help you to add meaningful notes and information on relative items on images. And if you’re a freelancer building websites for clients, incorporating Ziltag into the project will bring more value to both you and your clients.

How Does It Work?

Ziltag binds every image to a Ziltag Map and its backend API identifies Ziltag Map by a tuple: API’s token, page’s URL and image’s source. However, browsers have a Same-origin policy that restricts cross domain requests, i.e. troublesome to access the backend, but it can be overcome by CORS (Cross-Origin Resource Sharing).

Ziltag uses MutationObserver heavily which is well supported by modern browsers to track images, it opens a door to serve several scenarios that change the image, such as the style on images or its ancestors might be changed, the host generates or destroys images on the fly, or images might be animated by a carousel, etc. There are many cases need dynamic management, and MutationObserverbacks Ziltag well.

Ziltag Reader is another important component in Ziltag, the CRUD (create, read, update and delete) all live in Ziltag Reader. In fact, Ziltag Reader is an iframe that pulls the content via the backend. To boost the operating speed, Ziltag plugin sends warmup commands to the Ziltag Reader via postMessage for cross-window messaging. Thus the user can read the content instantly when opens the Ziltag Reader.

Ziltag is eager to spread the information, every update of content (i.e. new tag created, content updated, etc.) is delivered to the user in realtime. Server-Sent Events make the magic happen. The iframe which embeds the Ziltag Reader receive an update message first, then it passes the message via postMessage to the frontend, no dummy pieces.

Ziltag presents contents directly on images as the blinking red dots — the tags, where others can click to read and discuss. Hover on tags will bring up a preview dialog where readers can read the summary directly. If you want to add a new tag, hover on the image and click the red “+” icon on top-right corner to bring up the pop up screen. Click anywhere on the image you would like to tag, and write your comments.


There are three primary systems in the architecture: an API server, an UI server, and a JavaScript plugin. The API server is a Rails server, it serves all data storage and generating, including the data of tags, comments, and social media data. The UI server is a NodeJS server, it defines the UI with React, and serves the UI pages. On the clients’ sites, we provide a JavaScript plugin which built with React too. The plugin embeds the UI page in an iframe, using cross-window messaging to communicate.

React Application

The UI server and the plugin are built with React, and we choose redux to manage the state of the application. With redux, it’s easy to scale the number of components because of the philosophy of dumb component.


Despite the dumb components serves the vision parts well, there are still some issues should be addressed in the development of the React application: how to route the page and how to manage the side-effects. We choose the classic react-router to handle the routes and use redux-saga to manage the side-effects. Because the react-router is classic, we just discuss redux-saga here.

According to the introduction of the redux-saga from the official README:

The mental model is that a saga is like a separate thread in your application that’s solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and canceled from the main application with normal redux actions, it has access to the full redux application state, and it can dispatch redux actions as well.

We handle the API calls, user events, and messaging with the redux-saga. And thanks to the excellent side-effects model, it’s easy to scale the number of the side-effects.


We choose Koa 2 to serve the NodeJS UI server. It works with a node proxy server to redirect the request reversely to the API server and route the react page accordingly.


We package the application with webpack; it’s excellent to modularize the react components and patch polyfill. We choose postcss to preprocess the CSS and Babel to compile the ES7. Thus we can write in the modern CSS and JavaScript like a charm.

Rails Application

Typically, our Rails application plays a role of API server, but it also renders some web pages as well (the landing page for example). Instead of looking for some external libraries for help (like react-rails), we released our gems, integrating Rails with Webpack and React.

Webpack On Rails

One of the gems we built is webpack_stats, which is built to replace sprockets-rails. Unlike other libraries, you don’t need to remember any view helpers like webpack_asset_path. Because it just overwrites the built-in helper compute_asset_path, every view helper in rails that you have already familiar with will just work. For more details, check the README file.

React On Rails

React can be easy to be introduced into Rails application if we only need client rendering. However, to improve SEO, server rendering is required as well. Actually, we should have chosen react-rails, but the only one reason why we abandoned it was its dependency of sprockets. Since we have replaced sprockets with Webpack, we made our own JS renderer using ExecJS.


We also released another gem to provide a common interface for performing complex user interactions, like what reactor does, but with only 20 lines codes and better performance.


To deliver every day, we automate several things, including the build of the application and environment, the deployment, and the alert of the system.


We encapsulate the API server and the UI server to Docker containers. With docker, the development and production are shared the same environment, and we use environment variables to control containers in different stages.

Continuous Integration

We used to deploy using Capistrano and be automated by Semaphore CI. After introduced Docker, things getting more complicated but worth it. For short, below is our development flow:

  1. Make some changes and push to Github.
  2. CI server (Semaphore) receives the notification from Github, build the Docker image for the revision, run tests, eventually, push the Docker image to Docker Hub.
  3. After a new docker image has been uploaded, the webhook send a request to our production server.
  4. There is a daemon for receiving docker webhooks in our production server after it receives a new notification, it starts to fetch newest docker image, restart all services, finally, removes previous docker images.


There are still some useful tools we employ in the development of Ziltag. We use hotel to manage the server and the port in the development environment, the hotel provides a proxy server to serve the dev domain and manage the port behind the scene, with hotel, developers can avoid the bootstrap work during development every day.

How To Deploy Ziltag?

Ziltag is composed with multiple micro-services which are encapsulated by Docker, 4 major repos are ziltag.com, ziltag-plugin, frontend-ziltag.com, and preview. Furthermore, there are also some utilities such as webpack_status, interactor2 and docker-web-hook which are created during the development of Ziltag.

Please follow our Deploy Instruction to host Ziltag on your website.

The following are brief introductions of our open-source repos:

Main Repos:

  • ziltag.com — The main official site of Ziltag, including API and some web pages like landing page and dashboard.
  • ziltag-plugin — It’s the core function of Ziltag, a plugin which can make images taggable on any web page.
  • frontend-ziltag.com — Ziltag Reader page.
  • preview — The preview server for anyone who wants to try Ziltag before registration.


  • webpack_status — It’s much like the gem “webpacker”, but just a plain webpack status file loader which can be used for both Ruby or Rails.
  • interactor2 — A small, simple and dependency-free service object library which can help you manage your logic object easier.
  • docker-web-hook — A server to hook hub.docker.com. In our use-case, it’s used to automatically deploy Ziltag by pushing new image to hub.docker.com.

We’re A Community!

Welcome to provide any feedback or suggestion you may have on each Github repo listed above, or file any issue on our Github community.

More by David Chang

Topics of interest

More Related Stories