URL Shortener

With Ambiorix we can build a URL shortener, it’s nothing impressive but an interesting and fun exercise. At it’s core a URL shortener accepts URLs and from it generates a shorter version. This is then stored internally so that when the generated URL is stored it simply redirects.

The project has this structure obtained from the CLI with ambiorix-cli create-basic shortener

.
├── DESCRIPTION
├── app.R
├── templates
│   └── home.html
└── views
    └── base.R

We do a simplified version of it, without a database; everything is stored locally in an R object (so everything is lost when the server is restarted). We hard-code the port as this will be necessary in order to return the shortened URL to the user. The idea is to have a simple form on the homepage to which people can submit URLs, this is POSTed, the server generates and returns the short URL. Then we add a method to listen to any incoming URL and redirects.

Note: We really oversimplify too much to keep this brief, there’s no error checking, or anything performed.

library(ambiorix)

PORT <- 5000

app <- Ambiorix$new()

# homepage
app$get("/", render_home)

app$post("/shorten/url", shorten)

# about
app$get("/:id", redirect)

# websocket 
app$receive("hello", \(msg, ws){
  print(msg)
  ws$send("hello", "Hello back! (sent from R)")
})

app$listen(PORT)
app$start()

The core of the app is in the views directory; hence it’s imported above.

The homepage (/) is an HTML file with the form (at the bottom of this page).

The shorten function is the handler for the POST to shorten/url. This stores generates a random URL from letters and stores that in a local data.frame called URLs.

This is then used in the redirect method which fetches the parameter id and redirects accordingly.

URLs <- data.frame()

# render homepage
render_home <- \(req, res){
  res$send_file("templates/home.html")
}

# render redirect
shorten <- \(req, res){  
  body <- parse_multipart(req)
  
  db <- data.frame(
    url = body$url,
    id = paste0(sample(letters, 4), collapse = "")
  )
  URLs <<- dplyr::bind_rows(URLs, db)

  url <- sprintf("http://localhost:%s/%s", PORT, db$id)
  res$send(htmltools::p("Visit:", htmltools::tags$a(href = url, url)))
}

# render redirect
redirect <- \(req, res){

  id <- req$params$id

  stored <- dplyr::filter(URLs, id == id)

  res$redirect(stored$url)
}

This is the homepage containing the form to submit URLs.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Ambiorix</title>
</head>
<body>
  <h1>URL shortener</h1>
  <form action = "/shorten/url", enctype = "multipart/form-data", method = "POST">
    <p>
      <label for="url">URL to shorten</label>
      <input type="text" name = "url">
    </p>
    <input type="submit">
  </form>
</body>
</html>