5  Hello, Again

There are many terrible mistakes to make in program design, and you should go ahead and make them at least once so that you understand them.

A sense of what a good program looks like is developed with practice, not learned from a list of rules.

Eloquent JavaScript, Marijn Haverbeke.

In the hello world chapter, we gave you this simple program and lots of text to digest:

library(ambiorix)

app <- Ambiorix$new()

home_get <- function(req, res) {
  res$send("Hello, World!")
}

app$get("/", home_get)

app$start(port = 3000L)

It has one endpoint and sends back a string as a response.

But if I keep hammering you with more text I will have denied you the joy of doing and thinking.

This chapter is full of code snippets and small explanations. Each one of them shows you what’s possible.

5.1 HTML

5.1.1 Strings

Sending HTML strings would work just fine…

library(ambiorix)

app <- Ambiorix$new()

home_get <- function(req, res) {
  res$send("<h3>Hello, World!</h3>")
}

app$get("/", home_get)

app$start(port = 3000L)

5.1.2 htmltools

But you’ll almost always want to use {htmltools}:

  • Allows you to write HTML but as R
  • Easier to create reusable HTML components
library(ambiorix)
library(htmltools)

app <- Ambiorix$new()

home_get <- function(req, res) {
  html <- tags$h3("Hello, World!")
  res$send(html)
}

app$get("/", home_get)

app$start(port = 3000L)

5.1.3 Multipages

Link back and forth between pages:

library(ambiorix)
library(htmltools)

home_get <- function(req, res) {
  html <- tagList(
    tags$h3("Hello, World!"),
    tags$a(
      href = "/about",
      "Learn more about us."
    )
  )
  res$send(html)
}

about_get <- function(req, res) {
  html <- tags$div(
    tags$h3("About Us"),
    tags$p("We're the Ambiorix R web framework."),
    tags$a(
      href = "/",
      "Go back home"
    )
  )

  res$send(html)
}

app <- Ambiorix$new()

app$get("/", home_get)
app$get("/about", about_get)

app$start(port = 3000L)

5.1.4 Components

When dealing with HTML, you will work a lot with buttons, inputs, and forms.

library(ambiorix)
library(htmltools)

#' Home UI
#'
#' @param counter_value Integer /// Optional.
#'                      Current counter value.
#'                      Defaults to zero.
#'
#' @return [htmltools::tags]
#'
#' @export
UI <- function(counter_value = 0L) {
  tags$form(
    action = "/",
    method = "post",
    enctype = "multipart/form-data",

    tags$h3("Counter"),
    tags$p(
      "Current count is:",
      tags$strong(counter_value)
    ),

    tags$input(
      type = "hidden",
      name = "counter_value",
      value = counter_value
    ),
    tags$button(
      type = "submit",
      "Add 1"
    )
  )
}

#' Handle GET at '/'
#'
#' @export
home_get <- function(req, res) {
  res$send(UI())
}

#' Handle POST at '/'
#'
#' @export
home_post <- function(req, res) {
  data <- parse_multipart(req = req)
  old_value <- data$counter_value
  new_value <- as.integer(old_value) + 1L

  html <- UI(counter_value = new_value)

  res$send(html)
}

app <- Ambiorix$new()

app$get("/", home_get)
app$post("/", home_post)

app$start(port = 3000L)

By now you can see that this is just like any other day of writing R code, the only difference being we’re sharing our “results” via a web server.

5.2 JSON

When building data APIs for your frontend team to consume, you will most likely use JSON (JavaScript Object Notation).

5.2.1 Basic

res$json() serializes your objects to JSON format.

library(ambiorix)

home_get <- function(req, res) {
  response <- list(
    msg = "Hello, World!"
  )

  res$json(response)
}

app <- Ambiorix$new()
app$get("/", home_get)
app$start(port = 3000L)

When you run the app and view it on your browser, you will see the JSON representation of your data.

5.2.2 Dataframes

Yes, you can send data.frames too:

library(ambiorix)

home_get <- function(req, res) {
  response <- list(
    msg = "Hello, World!"
  )

  res$json(response)
}

mtcars_get <- function(req, res) {
  response <- head(mtcars)

  res$json(response)
}

app <- Ambiorix$new()

app$get("/", home_get)
app$get("/mtcars", mtcars_get)

app$start(port = 3000L)

Note the difference between the JSON representation of a named list and a data.frame: one is a JavaScript object, while the other is an array of objects.

5.3 HTML and JSON

By now you’ve noted that there’s no new syntax to learn when sending HTML or JSON.

Let’s serve both HTML and JSON from the same application, under different endpoints.

library(ambiorix)
library(htmltools)

#' Handle GET at '/'
#'
#' @export
home_get <- function(req, res) {
  html <- tagList(
    tags$h3("Hello, World!"),
    tags$a(
      href = "/about",
      "Learn more about us."
    )
  )
  res$send(html)
}

#' Handle GET at '/about'
#'
#' @export
about_get <- function(req, res) {
  html <- tags$div(
    tags$h3("About Us"),
    tags$p("We're the Ambiorix R web framework."),
    tags$a(
      href = "/",
      "Go back home"
    )
  )

  res$send(html)
}

#' Handle GET at '/api'
#'
#' @export
api_home_get <- function(req, res) {
  response <- list(
    msg = "Hello, World!",
    links = list(
      about = list(
        href = "/about",
        text = "Learn more about us."
      )
    )
  )

  res$json(response)
}

#' Handle GET at '/api/about'
#'
#' @export
api_about_get <- function(req, res) {
  response <- list(
    msg = "About Us",
    text = "We're the Ambiorix R web framework.",
    links = list(
      home = list(
        href = "/",
        text = "Go back home"
      )
    )
  )

  res$json(response)
}

app <- Ambiorix$new()

app$get("/", home_get)
app$get("/about", about_get)

app$get("/api", api_home_get)
app$get("/api/about", api_about_get)

app$start(port = 3000L)

Note how in the same application we have two representations of the “same” data:

  • HTML: Human friendly. This is what you want people to see when they visit your web application.
  • JSON: Machine friendly. This is the format you want to use when building data APIs so that other programs can interact with your program.