Response

Every route (get, post, etc.) handler should accept the request (req) and the response (res). Note that routes may optionally accept a different handler for errors.

To learn more about any of the response methods described below, please see ?ambiorix::Response.

HTML

Send plain HTML with send.

app$get("/html", \(req, res) {
  res$send("<h3>hello, world!</h3>")
})

htmltools

It’s often more convenient to use {htmltools} because it:

  1. Allows you to write HTML as structured R code.
  2. Makes your code more readable when generating complex HTML dynamically.
library(htmltools)

app$get("/", \(req, res){
  res$send(tags$h3("hello, world!"))
})

app$get("/about", \(req, res) {
  html <- tagList(
    tags$h3("About Us"),
    tags$p("The Ambiorix R Web Framework")
  )

  res$send(html)
})

Reprex

Here’s a reprex using Bootstrap:

Click to expand example
library(ambiorix)
library(htmltools)

#' Generic HTML page
#'
#' @param ... Passed to the body tag of the html document.
#' @return [htmltools::tags$html]
#' @export
page <- \(...) {
  tags$html(
    lang = "en",
    tags$head(
      tags$meta(charset = "utf-8"),
      tags$meta(
        name = "viewport",
        content = "width=device-width, initial-scale=1"
      ),
      tags$title("HTML demo"),
      tags$link(
        href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
        rel = "stylesheet",
        integrity = "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH",
        crossorigin = "anonymous"
      )
    ),
    tags$body(
      class = "bg-light",
      ...
    )
  )
}

#' Home page
#'
#' @export
home_page <- \() {
  bgs <- c("primary", "success", "secondary", "dark", "danger", "white", "light")
  bg_divs <- lapply(bgs, \(bg) {
    class <- c(
      "col-12 col-md-4",
      "border border-dark-subtle",
      paste0("bg-", bg)
    )

    tags$div(
      class = class,
      style = "min-height: 250px"
    )
  })
  bg_divs <- tags$div(
    class = "row",
    bg_divs
  )

  page(
    tags$div(
      class = "container vh-100 bg-white",
      tags$h3("Hello, World!"),
      tags$p("This example is using bootstrap 5.3.3"),
      bg_divs
    )
  )
}

#' Handler for GET at '/'
#'
#' @export
home_get <- \(req, res) {
  res$send(home_page())
}

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

Sendf

A convenient wrapper around sprintf and the send.

app$get("/text", \(req, res){
  res$sendf("Hello %s", req$user)
})

Text

Send a plain text with text.

app$get("/text", \(req, res){
  res$text("hello!")
})

File

An .html or .R file can also be used as response.

# sends templates/home.html
app$get("/file", \(req, res){
  res$send_file("home.html")
})

Render

An .html, .md or .R file can also be rendered. The difference with send_file is that it will use data to process [% tags %]. You can read more it in the templates documentation.

# renders templates/home.html
# replaces [% title %]
app$get("/:book", \(req, res){
  res$render("home.html", data = list(title = req$params$book))
})

# renders docs/index.md
app$get("/docs", \(req, res) {
  res$render("index.md")
})

JSON

You can also send JSON responses with json, e.g.: to build an api

app$get("/:book", \(req, res){
  res$json(cars)
})

Status

The HTTP status of the response can be specified in two ways:

  • status active binding
  • set_status() method
app$get("/error", \(req, res){
  res$status <- 500L
  res$send("Error!")
})

# or
app$get("/error", \(req, res){
  res$set_status(500L)$send("Error!")
})

CSV

Serialises to CSV, when this endpoint is visited the CSV file is downloaded. It takes the data as first argument and the name of the file to download as second argument.

app$get("/csv", \(req, res){
  res$csv(cars, "cars-data")
})

TSV

Serialises to tab separated file; it takes the same arguments as the csv response.

app$get("/tsv", \(req, res){
  res$tsv(mtcars, "more-cars")
})

Image

To send .png or .jpeg files, use the image() method:

app$get("/cute-cat", \(req, res) {
  res$image(file = "/path/to/local/file")
})

If you prefer, you can be more specific and use the png() & jpeg() methods:

app$get("/cute-cat-png", \(req, res) {
  res$png(file = "/path/to/local/png/file")
})

app$get("/cute-cat-jpeg", \(req, res) {
  res$jpeg(file = "/path/to/local/jpeg/file")
})

ggplot2

Send a ggplot2 plot using the ggplot2() method:

app$get("ggplot", \(req, res) {
  # make the plot:
  p <- ggplot2::ggplot(
    data = iris,
    mapping = ggplot2::aes(
      x = Sepal.Length,
      y = Petal.Width,
      color = Species
    )
  ) +
    ggplot2::geom_point() +
    ggplot2::theme_bw()

  res$ggplot2(plot = p, type = "jpeg") # or "png"
})

Any further parameters given to res$ggplot2() are passed to ggplot2::gsave() function. eg. width & height.

htmlwidgets

Serialises an htmlwidget.

library(echarts4r)

app$get("/htmlwidget", \(req, res){
  plot <- e_charts(cars, speed) %>% 
    e_scatter(dist)
  res$htmlwidget(plot)
})

Headers

You can add headers with the header method on the response object.

app$get("/hello", \(req, res){
  res$header("Content-Type", "something")
  res$send("Using {ambiorix}!")
})

If you have several headers, put them in a named list and use the set_headers() method:

app$get("/hello", \(req, res) {
  headers <- list(
    "Content-Type" = "something",
    "Access-Control-Allow-Origin" = "https://ambiorix.dev",
    "Header-Name" = "Header Value"
  )
  res$set_headers(headers)
  res$send("Using {ambiorix}")
})

In addition, there are several methods for setting the Content-Type header of the response. These will come in handy when you’ve written your own custom serializers. They all have the prefix header_content_:

  • res$header_content_json()
  • res$header_content_html()
  • res$header_content_plain()
  • res$header_content_csv()
  • res$header_content_tsv()

Cookies

To set a cookie, use the cookie() method:

app$get("/hello", \(req, res) {
  today <- as.character(Sys.Date())
  res$cookie(name = "today", value = today)
  res$send("Hello! Cookie 'today' has been set.")
})

To clear a cookie, use the clear_cookie() method:

app$get("/hello2", \(req,res) {
  res$clear_cookie(name = "today")
  res$send("Cookie 'today' cleared!")
})

Redirect

One can also redirect to a different url, note that these should have a status starting in 3.

app$get("/redirect", \(req, res){
  res$redirect("/", status = 302L)
})

Hooks

Hooks are functions that run before or after rendering, allowing for pre-processing and post-processing of content.

Pre-render hooks

A pre-render hook runs before the render() and send_file() methods. Pre-render hooks are meant to be used as middlewares to, if necessary, do pre-processing before rendering.

It must accept at least 4 arguments:

  • self: The Request class instance.
  • content: String. [File] content of the template.
  • data: Named list. Passed from the render() method.
  • ext: String. File extension of the template file.

Include ... in your hook to ensure it will handle potential updates to hooks in the future.

The pre-render hook must return an object of class ‘responsePreHook’ as obtained by ambiorix::pre_hook().

my_prh <- \(self, content, data, ext, ...) {
  data$title <- "Mansion"
  pre_hook(content, data)
}

#' Handler for GET at '/'
#' 
#' @details Renders the homepage
#' @export
home_get <- \(req, res) {
  res$pre_render_hook(my_prh)
  res$render(
    file = "page.html",
    data = list(
      title = "Home"
    )
  )
}

In the above example, even though we have provided the title to render() as “Home”, it is changed in my_prh() to “Mansion”.

Post-render hooks

A post-render hook runs after the rendering of HTML. It must be a function that accepts at least 3 arguments:

  • self: The ‘Response’ class instance.
  • content: String. [File] content of the template.
  • ext: String. File extension of the template file.

Also, include ... in your hook to ensure it will handle potential updates to hooks in the future.

Ideally, it should return the content.

my_prh <- \(self, content, ext, ...) {
  print("Done rendering!")
  content
}

#' Handler for GET at '/'
#'
#' @details Renders the homepage.
#'
#' @export
home_get <- \(req, res) {
  res$
    post_render_hook(my_prh)$
    render(
    template_path("page.html"),
    list(
      title = "Home",
      content = home()
    )
  )
}

After each render on the home page, my_prh() will print “Done rendering!” on the console.

Setting a Global Hook

You can set a global pre-render or post-render hook using middleware.

This is useful when you need to ensure that all rendering operations automatically apply the hook without explicitly setting it in every route.

Consider the following template, page.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <title>[% title %]</title>
</head>

<body>

  <div>
    [% content %]
  </div>

</body>

</html>

And the corresponding index.R:

library(ambiorix)

#' A pre-render hook
#'
#' @param self The request class instance.
#' @param content String. [file] content of the template.
#' @param data Named list. Passed from the [render()] method.
#' @param ext String. File extension of the template file.
my_prh <- \(self, content, data, ext, ...) {
  data$title <- "Mansion"
  pre_hook(content, data)
}

#' Middleware to set a global pre-render hook
#'
#' @export
m1 <- \(req, res) {
  res$pre_render_hook(my_prh)
}

#' Handler for GET at '/'
#'
#' @details Renders the homepage
#' @export
home_get <- \(req, res) {
  res$render(
    file = "page.html",
    data = list(
      title = "Home",
      content = "<h3>hello, world</h3>"
    )
  )
}

Ambiorix$new(port = 5000L)$
  set_error(error_handler)$
  use(m1)$
  get("/", home_get)$
  start()

Notice how even though res$render() sets the title as "Home", the global pre-render hook my_prh(), modifies it to "Mansion".

Since the middleware m1 is applied globally, this change affects all rendering operations across the application.