Building a Personal URL Shortener, Part 1: Serving URLs


As I’ve been bringing my blog back to life, it seemed useful to keep a list of links and share them with a tiny URL service. Here’s what I did to get that started on App Engine.


First I registered a “short” domain, timbx.me, to use for my links. This is optional, but having a separate domain allows me to use it just for links; now I can treat all requests in the timbx.me domain as links.

At first I thought this would require a separate App Engine app to serve bookmarks, but I found that I could easily serve these links with the same App Engine app that I use to run my blog. I just have to point my new domain at my App Engine app and code the app to respond specially for timbx.me links. Since I’m using gorilla/mux, I was able to do that with a few new lines in my app’s init method:

r := mux.NewRouter()
....
s := r.Host("timbx.me").Subrouter()
s.HandleFunc("/{tinyurl}", tinyURLHandler)

Here I tell my main router that I want a “subrouter” that will only serve requests to the timbx.me domain. Then I add a single URL handler that will respond to tiny URL requests by redirecting to the corresponding URLs.

Representing Links

To represent links, I added this struct to my blog app:

type Bookmark struct {
    URL     string         `datastore:"url"`
    Created time.Time      `datastore:"created"`
    Updated time.Time      `datastore:"updated"`
    Note    string         `datastore:"note"`
    Tags    []string       `datastore:"tags"`
    Key     *datastore.Key `datastore:"-"`
}

I’m storing bookmarks using Google’s Cloud Datastore API, and the datastore field tags indicate that I want to use lowercase field names in my datastore storage (that’s optional).

You might notice that there’s no field in the bookmark struct for the tinyurl. That’s because I’m storing bookmarks using tinyurls as their primary keys. Also, the field tag for Key marks it to be excluded from the datastore. I put a Key field in the struct so that when it’s useful, I can set it in bookmarks after they’ve been read from the datastore.

Serving Links

Here’s the handler that serves tinyurl links. Since the bookmark lookup uses the tinyurl as its primary key, lookups are simple and fast.

func tinyURLHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    tinyurl := vars["tinyurl"]
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Bookmark", tinyurl, 0, nil)
    var bookmark Bookmark
    if err := datastore.Get(c, key, &bookmark); err == nil {
        http.Redirect(w, r, bookmark.URL, 303)
        return
    } else {
        http.Redirect(w, r, "http://timburks.me", 303)
    }
}

If a bookmark doesn’t exist, I just redirect the request to my blog.

Saving Links

Finally, I save bookmarks with this function (more on this in a future post):

func SaveBookmark(c appengine.Context, tinyurl, path string) {
    b := &Bookmark{}
    b.URL = path
    b.Created = time.Now()
    b.Updated = b.Created
    key := datastore.NewKey(c, "Bookmark", tinyurl, 0, nil)
    _, err := datastore.Put(c, key, b)
    if err != nil {
        log.Printf("ERROR %+v", err)
    }
}

That’s enough to serve links! Here are two that you can follow to learn more about Go (timbx.me/go) and App Engine (timbx.me/appengine).