Creating a R web application with Nuxt and Plumber

Published Nov 08, 2017Last updated Jan 17, 2018
Creating a R web application with Nuxt and Plumber

The Challenge

R is an open source programming language and software environment for statistical computing and graphics. Today R is often developed by scientists, statisticians or analytics on a desktop using scripts. It is not made for web application in the first place. However, there's increasingly a need for the interaction between both. Shiny is a popular option for delivering R in web apps currently. Though, Shiny is an opinionated framework as it has it own way generating HTML and CSS for you. It seems impossible to create your own frontend interface.

Solutions

There are couple of options if you are looking for a micro framework instead of a fullstack framework like Shiny. You can develop the stacks of technology by your own and that give you more flexibility. They are Plumber and jug. The main problem with them is the lack of community support and their documentations seems incomplete and under development. Below are some example codes how they work.

  1. Plumber

plumber.png

# myfile.R

#* @get /mean
normalMean <- function(samples=10){
  data <- rnorm(samples)
  mean(data)
}

#* @post /sum
addTwo <- function(a, b){
  as.numeric(a) + as.numeric(b)
}
# index.R

#!/usr/bin/Rscript

library(plumber)
r <- plumb("myfile.R")  # Where 'myfile.R' is the location of the file shown above
r$run(port = 8000)
  1. jug

beer_jug_small.png

# index.R

#!/usr/bin/Rscript

library(jug)

jug() %>%
  get("/", function(req, res, err){
    "Hello World!"
  }) %>%
  simple_error_handler_json() %>%
  serve_it()

The frontend

download.png

Now, you have the basic idea how you can use either of these as your backend stack. As for the frontend, you can choose Nuxt, Next, Angular Universal, or just a plain HTML + CSS. I have been exploring Nuxt, so this article shows you how you can integrate it with R for a web application. I chose Plumber for this exercise.

Here is an example welcome page in Nuxt template:

// pages/index.vue
<template>
  <div>
    <h1>Welcome!</h1>
  </div>
</template>

The concept

The idea of this solution is using Nuxt to run as the view for the backend, R. We only use Nuxt routes for public users. axios is used inside Nuxt pages to call the backend routes. Then the backend spits JSON or image data to the pages in Nuxt, an example:

// home page

<template>
  <div>

    <!-- grid-container -->
    <div class="grid-container">
      <div class="grid-x grid-padding-x">
        <div class="medium-12 cell">
          <img src="~/assets/img/logo.png" alt="Nuxt.js Logo" class="logo float-center" />
        </div>
      </div>
    </div>
    <!-- grid-container -->

    <!-- grid-container -->
    <div class="grid-container">
      <div class="grid-x grid-padding-x">
        <div class="medium-12 cell">
          <div class="text-center">
            <h1>{{ message }}</h1>
            <p>This is Nuxt + R.</p>
            <nuxt-link to="/iris" class="button">Iris</nuxt-link>
          </div>
        </div>
      </div>
    </div>
    <!-- grid-container -->

  </div>
</template>

<script>
import axios from '~/plugins/axios'

export default {
  async mounted () {
    // Initiate foundation.
    // Must do it after Vue has rendered the view.
    $(document).foundation()
  },
  async asyncData () {
    var {data} = await axios.get('/')
    var data = data.data

    var props = {
      message: data.message,
    }
    return props
  },
  head () {
    return {
      title: 'Nuxt + R',
      meta: [
        { hid: 'description', name: 'description', content: 'Home page' }
      ],
    }
  }
}
</script>

The magic is here:

var {data} = await axios.get('/')

axios pulls the output from R/ plumber when the home page is loaded and this is what you could do in your backend side:

# app.R

#* @serializer unboxedJSON
#* @get /
function(){
  status <- 200
  message <- 'Hello World'

  list(
    status = 200,
    data = list(
      message = message
    )
  )
}

If you are serving image as the output, for example:

# app.R

#* Plot out data from the iris dataset
#* @serializer contentType list(type='image/png')
#* @post /iris
function(req, res) {
  dataset <- iris
  title <- 'All Species'
  
  # Filter if the species was specified
  if (!is.na(species) && !species == '') {
    title <- paste0("Only the '", species, "' Species")
    dataset <- subset(iris, Species == species)
  }
  
  plot(dataset$Sepal.Length, dataset$Petal.Length,
    pch=22, bg=c("red","green3","blue"),
    main=title, xlab="Sepal Length", ylab="Petal Length")
}

Then what you need to do in Nuxt, for example:

// page/iris/index.vue

...
...
let response = await axios({
  method: 'post',
  url: '/iris',

  // Stringify the object so you get: plot=scatter&species=setosa
  data: querystring.stringify(this.form),

  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  responseType: 'arraybuffer'
})

// Handle response image type.
var imageData = new Buffer(response.data, 'binary').toString('base64')

// Update the image data.
this.image = 'data:image/jpeg;base64, ' + imageData
...
...

In your Nuxt template:

// page/iris/index.vue

<div class="medium-9 cell">
   <img :src="image" />
</div>

That's it. Just be sure you have CORS enabled in your R app, e.g.:

# app.R

#* @filter cors
function(res) {
  res$setHeader('Access-Control-Allow-Origin', '*') # Or whatever
  plumber::forward()
}

Using the application

You can download or clone the basic applications above in GitHub: Nuxt + Plumber.

Be sure you have R and these packages installed:

library(jsonlite)
library(ggplot2)
library(magrittr)
library(dplyr)
library(urltools)
  1. Go to the server/ directory, install the packages and run the app:
# Production build
$ ./index.R

Access it at http://localhost:8000/

  1. Go to the client-nuxt/ directory, install the packages and run the app:
# Dependencies
$ npm install

# Development
$ npm run dev

# Production build
$ npm start

Access it at http://localhost:3000/. You should see a home page with Hello World message coming from the separate port at http://localhost:8000/:

app01.png

If you access the app at http://localhost:3000/iris, you should see the iris dataset has been plotted into a graphic which is coming from http://localhost:8000/iris:

app02.png

Conclusion

None of these frameworks is perfect. I have been looking for a solid R micro framework that is similar to Slim, Express or Koa, but it seems that Plumber or jug is the closest option if you have struggled with Shiny and I hope this article helps. Let me know what you think and what technologies you would use for your projects. Any suggestions and corrections, please leave a comment below.

Discover and read more posts from LAU TIAM KOK
get started