Our New Office

This is where all the magic happens:

eTilbudsavis By The Numbers

Things are moving fast at eTilbudsavis. In fact, it’s moving so fast that I haven’t had the time to really sit down and reflect on how far we’ve come.

A few weeks ago we reached 1,000,000 downloads. Just think about that for a second. 1,000,000 downloads in Denmark. A country with a population of 5,590,000. I think that’s impressive. While download numbers are really cool, they don’t really share the entire story. 1,000,000 downloads are worthless if people don’t use the app. Together with our backend developer Henrik, we’ve prepared some interesting metrics and charts we think you might find interesting:

1,000,000
downloads on iPhone, iPad, Android, and Windows Phone/8

~460,000 monthly unique users

~200,000 weekly unique users

~5 avg. session duration in minutes

~3.700,000 catalogs being read each month

~3,000,000 clicks on offers each month

Requests per second

To me it’s simply stunning that around 460,000 people use our product each month. Hopefully we’ll be able to make a way more detailed blog post in the future. I just wanted to express my gratitude to the team, management, and users for allowing me to be a part of this awesome thing we’re trying to build together.

Also a big thank you to our backend developer, Henrik, for providing the awesome graph showing how many requests per second our API handles.


We want to be the best shopping buddy. If you believe in this mission, we might have a job for you.

We're Hiring

It’s been a while since our last blog post and a lot has happened since then. Luckily, we still have a lot more we want to do and change. That is why we’re hiring more talented people to help us build the future retail platform.

  • DevOps Engineer: To help us scale we need an engineer who can maintain and develop our AWS infrastructure.
  • Front-End Engineer: We love the web, and to keep pushing on this platform we need a kick-ass front-end engineer to be responsible for our single-page app.
  • Data Visualization Front-End Engineer: The term “Big data” isn’t necessarily a term of our liking but we do have a huge amount of data generated every day that we want to deliver in an engaging and useful manner to help retailers navigate the retail space.

In the coming days, we’ll be adding two backend engineering job postings so be sure to check our always updated job page. We look forward to hearing from you.

Simon Says... Hello!

I’m Simon, the new backend developer here at eTilbudavis. One of the things I enjoy the most is the art of writing code, making code look and work just right.

When I was nine or ten, my brother introduced me to Flash, the new animation tool from Macromedia. I spent hours upon hours creating small animations in Flash. Then one day, Macromedia decided to add a scripting language called ActionScript to Flash and subsequently changed my life forever.

I spent my early teenage years creating games in Flash and soon moved on to creating websites and applications in many different programming languages and writing documentation for open source projects. I was determined that I would one day make a career writing code.

During my gap year before university, I went backpacking in China. That experience made me forget about coding for a while and instead I went to the Copenhagen Business School intent on learning Chinese. That track also brought me back to China and also to Korea several times. During my years at CBS I lived and breathed Asian language and culture.

Four years, one degree, and two Asian languages later, I had had enough of speaking about marketing and GDP growth in Mandarin. Instead of talking to Chinese businessmen for the rest of my life, I decided to enroll in the Software Development MSc programme at the IT University of Copenhagen, revisiting my lost teenage passion. That turned out to be just the right decision.

Now I spend my time attending classes and doing pervasive computing research at ITU while working on new backend projects here at eTilbudsavis, writing new code almost every day of the week. I could really not think of a better way to spend my time.

A new backend developer

It has been a very busy summer at eTilbudsavis. In fact, we are so busy that we have begun looking for a new part-time backend developer.

If you are a student, this is a great opportunity to get some hands-on experience whilst still keeping up with your studies.

Your skills and Experience

  • Fast learner
  • At least 3 years of programming experience (all experience counts).
  • Willing to work on-location at our office in Field’s.
  • *nix experience.
  • PHP and Python experience is a plus.

At eTilbudsavis we pick the right tool for the job. This sometimes mean that you need to learn a new language. Therefore, being a fast learner is essential to working here.

We use git with GitHub for all our version-control needs, so knowing git is a plus.

We know that no student has 3 years of professional experience, but many contribute to open source projects or have small personal ones. We would very much like to see examples of your work. If you use GitHub, just send us your profile link.

The entire development team use either OSX or Linux (or both) and all our servers run either Ubuntu or a CentOS derivate.

We Offer

  • A Great office at Field’s, right by the metro station.
  • Flexible hours.
  • Young team.
  • Great learning environment.
  • Coffee, Red Bull, Cola, {insert your drink of choice here}.

Your Job

Your job is to develop and maintain internal tools and APIs. You most likely will begin your work by helping with some of the ongoing projects like

  • Improving our rendering capabilities of PDFs.
  • Creating a unified internal email API with template and i18n features.

Sounds interesting? Let’s talk. Send us an email on jobs+backend@etilbudsavis.dk.

Code Camp

The development team at eTilbudsavis went on a two day code camp to unfamiliar surroundings in Nykøbing Sjælland far away from daily life. It was a great learning experience for all of us and it was a great way to introduce our new iOS developer, Laurie, to the team.


image


We rented a place right next to the marina and ocean, which was a true delight. The fresh sea air and boats sailing past us was a nice touch.

Food and Internet

Filled refrigerator

With a filled refrigerator we were ready to take on the two day coding spree. Red Bulls, Danish delicacys, coke, soda, chocolate milk, coffee, beer, candy, chips and almost any other unhealthy thing you could think of was present to help prevent us from falling asleep. Needless to say, we all agreed a salad was needed when we arrived home.

While the food was taken care of, the next most important thing was the internet connection. We quickly connected to the access point and ran speed tests. It was a 5/1 connection, which isn't a lot compared to our 100/100 connection at the office but it actually performed quite well over the two days with no connectivity issues.

The First Day

image


Before going to the code camp we talked about what we each wanted to accomplish there. Alex, who’s in charge of business intelligence, wanted to stress test and improve performance for a new service, Quetzal, which we’ll use for high performance counting internally (we might open source it at some point). Danny, who’s in charge of everything Android, wanted to complete the next version of the Android SDK. Laurie, who’s our new iOS developer, wanted to complete the next version of the iOS SDK like Danny. Henrik, who’s in charge of the backend and API wanted to complete a set of new endpoints for a future product launch for our customers in the retail business. Andreas, who’s our operations manager, wanted to optimize Zendesk tooling, which we use for support and come up with great ideas on how to improve internal tools. I wanted to do a lot of i18n work, iterate on our new website and start implementing some of the endpoints Henrik finished.

With each goal set, we started developing. A lot of interesting stuff occured that night. As the night progressed, each of us had new, exciting stuff to show to each other. Henrik had new API’s ready, Alex was stress testing like a boss, Danny finished the SDK (and even accomplished to do an app update to our 200.000 Android users with better thread management making the app feel way faster than before). Laurie managed to complete key components of the new SDK like session management, geolocation and API requests. Andreas was doing a lot of work with Zendesk and managed to respond to hundreds of tickets with new macros and a lot of other goodies. I managed to nail down a lot of i18n logic (more on that another time) and I did a lot of website design iterations, which are already in production. I also got to implementing Henrik’s new API’s, which were a delight to work with. Things move quickly indeed. Expect a lot of product launches.

Design Ping Pong

image


Laurie is a strong designer with a keen sense of UI and UX like Danny. The two working together brings it all to a whole new level. They get a chance to streamline the app experience across iOS and Android (with each app catering to the platform UI/UX guidelines, of course). They also get a chance to offer the same functionality in the SDK’s making it easier for developers to move between the two SDK’s.

1, 2, 3, …, n

Quetzal

Alex was quite busy working with Quetzal, which is a really complex yet elegant service. Quetzal allows us to count everything we can think of. Page views, offer views, catalog views, searches, shopping list interaction etc.

Most of the code camp for Alex was about stress testing and make the system completely fault tolerant. The tests included killing nodes unexpectedly and see the system recover from it and load balance events to other nodes that were online. The cool thing is that you can kill all nodes, which won't affect anything as the events just queue up and aren't lost. When nodes appear, they will be dispatched from the queue.

If we decide to open source it, I can imagine Alex doing a blog post about it so stay tuned.

Deep thoughts

API v2

Henrik did a lot of awesome work on the API at the code camp. He managed to complete almost all the GET endpoints. Next up is POST, UPDATE and DELETE endpoints. Other than this, he managed to cut quite a lot of milliseconds of API v2 responses. Now, requests take around 100-200 ms., which is insanely fast when you think about all the geolocation complexity the API has to handle and with different latitudes, longitudes and radiuses hitting the API all the time. You can read more about the API here and here. The API enables us to create all the awesome apps and website and it’s the backbone of the company.

The Next Day

image

Despite going to bed at 3 am. we were up five hours later. Whether it had something to do with Alex snoring like a goat or the massive amount of Red Bull and coffee I can’t tell ;-)

It was a great day, where a lot of code was pushed. We also went to the city for a walk and get some lunch and Danny and Henrik made lasagne for dinner.

image

Andreas did a lot of thinking on how to improve internal tools, which will include the use of WebSocket’s. Thank god we don’t have to support IE8+9 for that feature ;-)

All in all, it was a great couple of days. I can definitely recommend any tech company arranging a code camp far away from daily life. You are more productive, focused and the entire team gets to know each other way better than before.

Even though the blog post became lengthier than I imagined, I probably left a lot of what happened out. Oh, well…

A Quick Hello

Hi, I’m Laurie.

I’m really excited to be introducing myself as the new iOS developer at eTilbudsavis.

I’m a 30 year old British guy, and have lived in Denmark for the last 7 years. Like most foreigners living in Denmark, I came here because of a girl (who is now my wife), and haven’t looked back since.

As you can probably guess, I’m an Apple fanboy born and bred (literally in fact - my father bought an Apple IIe the year I was born). Prior to starting at eTilbudsavis, I was developing RAW editing software for the Mac, and before that the user interfaces for Sony’s Formula One game on the PS2/3/P. I’ve also been known to try my hand at creating database-driven website projects, and back in my foolish youth I even had dreams of becoming a music video director, studying Computer Animation and Film Special Effects at University (though ultimately found that coding and the maths-based classes were what I enjoyed most and spent my spare time doing).

In my newly minted role as the iOS guy of the office, I will be developing shiny new versions of the iPhone and iPad apps. There will also need to be a new Obj-C SDK to take advantage of all the benefits of the great new ETA API the other guys recently released.

With iOS 7 coming out later in the year there is obviously going to be a lot of work to make the eTilbudsavis look and feel as good as possible in the new operating system.

In fact, I’ve started already! I present to you, the new iOS7 look of the eTilbudsavis logo - now with a cleaner, more glow in the dark feel to it.

image

Job done, time for a drink! Jony Ive would be proud :)

Joking aside, I really can’t wait to get started - I can’t think of anything more exciting or satisfying than being involved in designing and making an app that is used and enjoyed by so many people every day, and to be working for a company that is energetic and not afraid of new technology.

I hope you all like what I contribute, and that every day we can make the product better.

The Largest Push

Today marks a great milestone in the history of eTilbudsavis. We’ve launched the second version of our REST API and a completely revamped website (currently in beta), which have improved in performance and will help us scale internationally. Months and months of work have finally seen the light of day and we’re all really excited.

REST API v2

Our backend developer, Henrik, will definitely cover this in more detail later on but I’ll briefly discuss the basics. Henrik has done a tremendous work of refactoring the entire REST API to run as an independent service on a PHP + Apache stack on AWS Elastic Beanstalk. The API is served at api.etilbudsavis.dk and it exposes relevant CORS headers to allow for cross-domain HTTP requests. It has increased in performance by 4x and it’s truly awesome. As a frontend developer, I appreciate a simple and clean API that allows me to do great work and not get in the way.

99% Client-side

You can see the new website here. It runs on a node.js + nginx stack on AWS Elastic Beanstalk and it’s scaled independently like the REST API. node.js is doing almost no work as everything is done on the client-side. node.js is only taking care of serving routes, rendering Jade templates and bundling translations in the HTML on page load. JavaScript on the client-side then takes care of business from there using RequireJS and Backbone as glue.

We wanted the website to work more like an app and not a traditional website. That means taking advantage of the entire screen, using HTML5 pushState for faster and more responsive page changes, getting rid of all unnecessary components and instead focus on the content. We really believe this is the way to go for developing awesome experiences.

New SDK’s

The new website is built on top of our new JavaScript SDK that abstracts a lot of functionality such as shopping lists and API requests. SDK’s for iOS and Android are on their way too.

Feedback

The rest of the week will for the most part be about bug fixing and hardening the new REST API and website to make for a full launch. As always, we’d appreciate any feedback you might have.

Now, however, it’s time for us to catch up on some lost sleep.

We Want You For Frontend

I know, what a cliché, right? Nevertheless, we do. eTilbudsavis is growing faster than we had ever imagined and to accommodate that growth we need talented people to help us do that.

Working at eTilbudsavis

Life at eTilbudsavis is pretty great. We reach out to many consumers, we’re not limited by old technologies, we have hack days, we drink beer, we laugh, we drink coffee, we prototype, we matter, then drink some more coffee and most importantly: we have fun. One of the things you’ll notice when entering the office, is the casual and open environment. People speak their mind and that ensures the best products.

Currently, we’re a total of four developers. Henrik (@henriktudborg) is working with our backend API and AWS, Alexander (@ahf) with business intelligence and invoicing, Danny (@dannyhvam) with our Android app and I’m (@mortenbo) doing the frontend for all of our products. We need a fifth developer who wants to help me out on the frontend. We are not looking for a ninja, magician or rockstar developer, but rather a serious, fast learning engineer with a passion for great design and clean architecture. You should have experience with the following:

  • JavaScript
    • jQuery
    • Backbone
    • Underscore
    • AMD/RequireJS
    • Mustache
    • Twitter Bootstrap
    • node.js
    • Feature testing
    • Browser security and cross-origin
    • Google Maps
  • CSS3
    • Media queries/responsive design
    • Cross-browser
    • Graceful degradation
    • Progressive enhancement
    • Stylus/nib
  • HTML5
    • Microdata/semantic
    • Jade
  • Tooling with Ruby, Python or Bash
  • UI/UX

But the most important thing is that you learn fast. Don’t know Ruby? You should be able to quickly learn it and work with it. That’s most important.

We’re in the process of internationalizing the concept to other countries than Denmark, which means you need to have knowledge of structuring our code in translation keys and make sure your code works across countries and not just a specific country.

Perks

You’ll get your own desk with a MacBook Pro and an external monitor and peripherals. There’s lunch every day, coffee and a filled refrigerator with snacks and beverages. You’ll get to be part of a revolution in the retail industry, where our mission is to make retail more intelligent together with cool people. Salary is based on your qualifications. The office is located on top of the largest shopping mall in Denmark: Fields, Copenhagen.

Assignment

Write a method to animate a given element with a callback when completed. It needs to able to animate the x and y axises using CSS3 transforms and hardware acceleration if supported and it should have a jQuery.animate fallback. It needs to work in all modern browsers. We’ll only accept Gist pastes emailed to jobs at etilbudsavis dot dk. Provide a link to your GitHub profile as well.

About eTilbudsavis

With more than 685.000 downloads and a popular website, eTilbudsavis is the most popular, digital platform for catalogs and offers in Denmark. eTilbudsavis is co-founded by the entrepreneurs Christian Birch (24) and Morten Bo (23). Recently Karsten Ree and Frank Ludvigsen (former Den Blå Avis) has become part of the investor group and talented developers have become part of the team such that eTilbudsavis can continue to be the preferred universe for catalogs and offers in Denmark, and soon more countries.

About Morten

Morten Bo I’m the co-founder and CTO of eTilbudsavis. It’s my job to make sure everything is running smoothly on the technical part of the business. I pride myself with great user interfaces and experiences. I was born and raised in Aalborg and moved to Copenhagen almost two years ago to work full-time on eTilbudsavis.


 

Business card

Working with complex SQL

Here at eTilbudsavis we use MySQL as our primary database. We are very happy with MySQL (except for the lack of a decent openGIS implementation (it is coming though)) and we have custom queries optimized for their specific task. A lot of them.

But working with long queries as strings is a nightmare, and when you start combining strings to form complex queries, the risk of error increases.

We still need the power of custom queries, so no catch-all model system would be able to help us here. And seeing as most of our queries share a lot of the same logic, a modular approach makes sense for us.

That is why we build a query building library to aid in the SQL building. The following QueryBuilder- and SQL-snippet is equivalent:

<?php
$qb = new QueryBuilder();
$queryString =
   $qb->with($pdo)
      ->select('*')
      ->from('db.table')
      ->where('db.table.col_int', 1) //integer
      ->where('db.table.col_bool', true) //boolean
      ->where('db.table.col_null', null) //boolean
      ->render();
?>
SELECT * FROM `db`.`table`
WHERE
     (
         (`db`.`table`.`col_int` = '1' 
      AND `db`.`table`.`col_bool` = '1'
      AND `db`.`table`.`col_null` IS NULL)
      );

Even though the php example is a few lines extra, it adds a great deal of flex- and readability. The only requirement is that you have a PDO object ready, seeing as QueryBuilder currently uses PDO’s quote method to escape input values.

In the above example there is little to gain from QueryBuilder, but when you combine it with additional PHP it becomes a powerful tool for building SQL. Consider the following example:

db.users
=========================================================
id | username | email       | age  | created             
---------------------------------------------------------
1  | John     | john@eta.dk | 25   | 2013-01-01 13:37:00 
2  | Foo      | foo@eta.dk  | 30   | 2013-01-01 13:37:00 
3  | Bar      | bar@eta.dk  | 21   | 2013-01-01 13:37:00 
<?php
$qb = new QueryBuilder();
$qb ->with($pdo)
    ->from('db.users')
    ->select('username');

if($includeEmail) {
  $qb->select('email');
}

$queryString = $qb->render();
// SELECT `username` FROM `db`.`users`
// SELECT `username`, `email` FROM `db`.`users`
?>

Here we apply additional logic to determine if we should select the email field.

By using an object we could also pass the query builder around, adding to it as we go:

<?php
function get_users() {
  $qb = new QueryBuilder();
  $qb ->with($myPdo)
      ->from('db.users')
      ->select('username')
      ->select('email');
  return $qb;
}

function where_age($builder, $age) {
  return $builder->where('age', $age);
}

$users21YearsOld = where_age(get_users(), 21);

$queryString = $users21YearsOld->render();
// SELECT `username`, `email` FROM `db`.`users` WHERE ((age = '21'))
?>

The double parentheses is intentional and can be optimized away if needed, though this optimization is not yet implemented, and wouldn’t add much performance anyway.

QueryBuilder supports most commom SQL operations via shortcuts, but if you need to write your own very specific part, you can use the Literal class to write SQL directly:

<?php

$qb = new QueryBuilder();
$qb ->with($myPdo)
    ->from('db.users')
    ->select('username')
    ->select('email');

$qb->where('created', new Literal('NOW() - INTERVAL 1 WEEK'), '>');

$qb->render();
// SELECT `username`, `email` FROM `db`.`users` WHERE ((created > NOW() - INTERVAL 1 WEEK))
?>

As a shortcut, Literal is also available as L, so the above where could be shortened down to

<?php
$qb->where('created', new L('NOW() - INTERVAL 1 WEEK'), '>');
?>

As you also might have noticed, comparing values with other than = is possible by adding the third argument to the where method. The available arguments are =, !=, >, >=, < and <=. QueryBuilder will do the proper conversions when dealing with null values, so

<?php
$qb->where('created', null, '!=');
?>

will be rendered correctly as

WHERE `created` IS NOT NULL

On the same note, dealing with arrays is also a lot easier with QueryBuilder, and arrays with null values are also handled correctly:

<?php

$qb = new QueryBuilder();
$qb ->with($myPdo)
    ->from('db.users')
    ->select('username')
    ->select('email')
    ->where('age', array(null, 21, 25));
$qb->render();
// SELECT `username`, `email` FROM `db`.`users` 
// WHERE (((`age` IS NULL OR `age` IN ('21','25'))))
?>

Ordering, grouping, joining and more is also avaiable directly on the QueryBuilder object. Here is another table to demonstrate joining:

db.roles
===================
users_id | role
-------------------
John     | admin
Foo      | user
Bar      | user
<?php
$qb = new QueryBuilder();
$qb ->with($myPdo)
    ->from('db.users')
    ->select('users.username')
    ->select('users.email')
    ->join('db.roles', //join db.roles table
      //where roles.user_id = the field users.id
      new Where('roles.users_id', new Field('db.users.id'))
    )
    ->select('roles.role');

$qb->render();
// SELECT `users`.`username`, `users`.`email`, `roles`.`role`
// FROM `db`.`users`
// JOIN `db`.`roles` ON
// (`roles`.`users_id` = `db`.`users`.`id`)
?>

Note the creation of a Field object. If you would have just written the name of the column as a string, the join look for a roles.users_id with the string value of "db.users.id". The Field is basically a Literal object with a bit of magic thrown in.

Also note that the join condition is no different from a regular where call. We created a new object from the Where class as the first argument to join.

This is exactly the same as calling the where method on a QueryBuilder object. The where method is just a shortcut for

<?php
$qb->register(new Where('db.table.column', 'some_value'));
?>

You can check out even more advanced examples in the QueryBuilder test suite on github .

With QueryBuilder, our SQL queries are much easier to maintain. We can create small modules to manage each part of the query (like limiting result sizes), use PHP to do conditional selects, pass the query around to different methods as it is being built (or even parts of the query, like a Where object). There are still a few quirks to figure out, and the rendering performance can be improved, but the overhead of a couple of objects is dirt cheap compared to the maintainability of our queries after switching to QueryBuilder.

We have open-sourced QueryBuilder, and you are more than welcome to use and improve it. https://github.com/eTilbudsavis/QueryBuilder/

Hello world!

Hi there! We’ve just launched our new technical blog! Here we’ll cover different aspects of the technical side of the service eTilbudsavis with some of most popular apps in Denmark. eTilbudsavis enables consumers to easily find catalogs, offers and stores nearby. We try to help consumers plan more efficiently by providing a shopping list too.

Use the top navigation to easily navigate to the source code of our SDK’s and documentation for the REST API. We’ll try to post as much as possible but since we’re a small and agile team we don’t always have time to do so.

Hack on.