'Systemet' – A liquor store status web app

About 3 min reading time

In Sweden the state has monopoly on all sales of alcohol. It sucks sometimes, especially when it comes to competition and opening hours. More than once I've discovered in panic that the liquor store closes 15.00 on a Saturday (!). I've felt a need for a small utility which tracks down the closest store and gives an answer to the question "Is the liquor store open?". So I built one.

Visit the app on systemet.johanbrook.com oppet.systmt.se. The source is on GitHub. I built it with mobiles in mind, but it's of course usable on all other web devices. If you're on iOS, feel free to add it to your homescreen for quick access and the full experience (I said that because I've made an icon and adjusted the app for native mode ...).

Thanks to Niklas Jakobsen for the awesome subdomain! ("Öppet" is Swedish for "open").

The "why"

  1. I wanted to build it
  2. I wanted to learn more about Node.js, MongoDB, CoffeeScript, and web APIs and services

The process

I started to research ways of getting a list of all stores along with their coordinates and opening hours. Seemed rare at first: 3rd party APIs only offered the product index. But it turned out Systembolaget has official, open API resources for this on this page. They are served in XML (uhh ..) and XLS (argh!) format, and are quite badly formatted.

On top of that, the coordinates for the store XML file are in RT90 format – not the "standard" WGS coordinate system. Since the app gets regular coordinates from the built-in GeoLocation sensor, I had to convert them from the XML file. My father is a surveyor, and along with some research I found neat formulas for converting RT90 to regular coordinates. The Gauss-Krüger method is common, and I also found existing Javascript code for the conversion.

After a lot of testing I could start doing the real thing. I began with getting to know Node.js, Express.js better along with basic conventions. Everything went quite fast since the language (Javascript) itself wasn't a problem, but instead the Node way of doing things were new to me. I separated the code in two parts:

  1. The main app which should serve a start page where client side JS would get the device's coordinates, send them as parameters via XHR to my Express app route which should find the closest store and send the data back as JSON.
  2. The import script which should get the XML from Systembolaget's website, parse it as JSON, and insert it into a MongoDB database.

I of course didn't want to get the whole XML file from the API on every app request, as it would be the only way of doing it. Systembolaget's "API" isn't that sophisticated. Instead I put it all into a Mongo database, which meant I could use all the very useful geospatial indexing features of MongoDB:

collection.ensureIndex loc: "2d"
collection.find(loc: $near: [50, 50])

Where [50,50] are the coordinates I would get from the client. Neat.


I began glueing everything together, and it worked quite well locally. Win! The closest liquor store showed up with the correct dates and hours. But I of course wanted to host it online for me and everybody. I instantly thought on Heroku since they offer Node.js hosting nowadays. They also have free MongoDB instances which would serve my purposes well enough. Since I needed to parse XML from Systembolaget's service when it's updated, a cronjob service was required. Heroku offers that (for free!) as well.

Node.js deployments on Heroku was something I had never done before – I've only worked with Ruby stacks. Turned out everything went smooth. After fiddling with the MongoDB connection for a while I got everything to work almost painlessly. Kudos to Heroku.


The user base of my web app is limited to Swedish readers, but it was fun hack nevertheless. I learned a lot about Node.js and asynchronous programming, which wasn't that easy to wrap my head around at first. Node.js was more low level web server stuff than I had previously done before.

Check out the source code in the GitHub repo and give feedback.