Table of Contents

Geekmarks

Hi everyone! So I wrote a new bookmarking service: Geekmarks, it's free and open-source (GitHub).

We already have a lot of bookmarking services, so why bother writing another one? Good question.

Rationale

In short, I want my bookmarking service:

I tried a lot of existing bookmarking services, and I wasn't satisfied by any of them, for a variety of reasons.

Let me elaborate on the organization part first. The simplest way to organize bookmarks is to introduce folders to group them. This still poses a well-known problem though: some bookmarks can logically belong to multiple folders. In order to address this issue, some services use tags: now we can tag a bookmark with more than one tag. So far so good.

Now, assume I have a generic tag programming, and a couple of more specific tags: python and c. I definitely want my bookmarking service to be smart enough to figure that if I tag some article with either python or c, it means programming as well; I don't want to add the tag programming manually every single time. So, what we need is a hierarchy of tags. Surprisingly enough, I failed to find a service which would support that.

This hierarchical tags thing was a major motivation for me to start Geekmarks.

Another important thing is that I want bookmarking service to be very quick to use. I don't want to go through these heavy user interfaces and look at all the eye candy. In my daily life I just want to either add a bookmark or find one, and I want to do that quickly: like, just a few keystrokes, and I'm done.

And last but not least, I love open-source. So, meet Geekmarks! A free, open-source, API-driven bookmarking service.

Overview

There's a backend (written in Go and PostgreSQL) which exposes a RESTful API, and various clients which talk to the backend.

Data storage

All the data is stored on the server; client does not store anything. Even when the user enters a tag name, and UI shows the autocomplete menu with matching tags, the menu contents are loaded from the server.

Of course, this has both advantages and disadvantages.

The good thing is that the arcitecthure is “clean”: clients are dumb, server is the one who does all the job. Among other things, this simplifies the process of writing new clients, and all the data is automatically synchronized between all user's clients with no extra effort.

Now let me consider disadvantages and argue why I still opted to use this design.

Project status

Backend is thoroughly tested and is very stable. API will be also frozen soon.

Client implementation, however, is not in a particularly good shape: it's a Chrome extension, and it's only a proof of concept. It's just barely good enough: it provides all the features and it's completely usable, but it's written in probably not a very good manner, its appearance is kinda rough, etc.

My plan is to rewrite the client in Angular 2, or maybe React, and hopefully I'll be able to factor out the most part and reuse it for many kinds of clients: web interface, browser extensions, and mobile phone apps (via Native Script or React Native). But that will be a long story, since I'm not really a frontend guy, and I'll need to learn a lot.

Demo

After installing the Chrome extension, click on the “g” icon:

Log in (via Google account), and when it's done, menu will look as follows:

Now, on any page you want to bookmark, click “Create bookmark” from the menu (or use a hotkey; I personally use Shift+Ctrl+B), enter tags for your new bookmark, and save it. Tags which don't yet exist will be created on the fly.

When you do that for the first time, it might look as follows:

Later, when you have more tags, the process of adding a new bookmark will be faster, like this:

And this is how you find your bookmarks, after all (I use a Ctrl+B hotkey for that):

Tips

Untagged bookmarks

Being able to organize bookmarks with hierarchical tags is really nice, but sometimes I'm too busy and I don't want to think carefully about where to put my bookmark; instead, I just want to quickly add it somewhere, and postpone the proper tagging for later. This is supported: when one wants to add current page as an untagged bookmark, it's as easy as Ctrl+Shift+B, Enter (provided that the keyboard shortcut is Ctrl+Shift+B).

Next time, when “Find bookmark” dialog is opened, all untagged bookmarks are shown immediately, before the user enters any tag; so, the user won't forget to tag them properly later.

Tag aliases

When editing a tag, one can specify multiple comma-separated names, and all of them will refer to the same tag. It's convenient in many cases, e.g. go and golang, jobs and careers, etc.

Note on hierarchical tags

I really love the hierarchical tags idea, and as I said, it was a major motivation for me to start Geekmarks. However, one should not misuse it.

For example, it's perfectly fine to place golang under programming, because Go, indeed, always implies programming.

But consider the case when we want to store something about terminal emulators, and we want to be more specific: terminal emulators which work under Linux. It would be a bad idea to create a hierarchy like /terminal/linux, because linux, of course, does not necessarily imply terminal. If we go with this, then we'll eventually end up with a bunch of tags like /terminal/linux, /programming/linux, /whatever-else/linux; and when we type linux in the tags input field, we'll get multiple tags with the same name, and we won't be able to just get “everything related to Linux”.

Instead, /linux should be a standalone tag, and the URL which describes something about terminal emulator under Linux should be tagged with both /terminal and /linux.

So, the rule of thumb is: before placing a tag foo under bar, ask yourself: does foo always imply bar? If yes, then go on. If no, then foo should be a separate tag.

That said, if you're unsure about how to arrange your tags, don't worry too much about it, since you can easily move your tags around later, and your bookmarks will be retagged appropriately.

Plans

I don't really know when I'll be able to implement all of it, but nevertheless, this is what I'd like to have sooner or later: