Application Structure

posit::conf(2023)
Shiny in Production: Tools & Techniques

It’s Never Just Shiny

… at least for production-quality apps!

  • External data sources
  • Connections to other execution backends
  • Additional R packages!

Application Structure Options

A Single Point: app.R

Prototype apps can coast by with a single app.R

  • More inputs, visualizations, modules, tabs …
  • Eventually the app.R almost explodes
  • Difficult to collaborate without conflicts

R Directory

  • Shiny supports auto-loading scripts in an R directory
  • Nested directories not supported
  • More information on the App Formats article

Enter the {golem}

Opinionated framework for building production-grade Shiny applications as R packages

  • Scripts guide you with first steps akin to {usethis} & {devtools}
  • Encourages Shiny best practices (especially modules)
  • Streamlines deployment on multiple platforms

{golem} project structure

├── DESCRIPTION
├── NAMESPACE
├── R
│   ├── app_config.R
│   ├── app_server.R
│   ├── app_ui.R
│   └── run_app.R
├── dev
│   ├── 01_start.R
│   ├── 02_dev.R
│   ├── 03_deploy.R
│   └── run_dev.R
├── inst
│   ├── app
│   │   └── www
│   │       └── favicon.ico
│   └── golem-config.yml
└── man
    └── run_app.Rd

Getting Started with {golem}

Use helper functions in dev/01_start.R to…

  • Create DESCRIPTION, README, LICENSE (etc.) files
  • Use git, tests
  • Add your own favicon 🚀

Developing with {golem}

Use the helper functions in dev/02_dev.R to…

  • Add R package dependencies
  • Create custom functions
  • Add Shiny modules

And use dev/run_dev.R to run your app

Deploying with {golem}

Use the helper functions in dev/03_deploy.R to…

  • Check your R package ( devtools::check() )
  • Build your R package ( R CMD build mypackage )
  • Generate deployment files for
    • RStudio Connect
    • Shinyapps.io
    • Your own Shiny Server
    • Dockerized deployments (e.g., ShinyProxy)

All About Modules

What are Modules?

Building blocks to compose any Shiny app out of smaller, more understandable pieces

  • Avoids namespace collisions when using same widget across different areas of your app
  • Allow you to encapsulate distinct app interfaces
  • Organize code into logical and easy-to-understand components
  • Facilitate collaboration

Sound familiar?

  • R functions also help avoid collisions in variable names with general R code
  • Essential for creating non-trivial and extensive workflows

Module Code Example

Anatomy of a Function (UI)

picker.R

set_picker_ui <- function() {
  tagList(
    selectInput(
      inputId = "set_num",
      label = "Select a set"
      choices = c("set1", "set2"),
      selected = "set1",
      multiple = FALSE
    )
  )
}

Anatomy of a Module (UI)

mod_picker.R

set_picker_ui <- function(id) {
  ns <- NS(id)
  tagList(
    selectInput(
      inputId = ns("set_num"),
      label = "Select a set"
      choices = c(),
      multiple = FALSE
    )
  )
}

Anatomy of a Module (UI)

mod_picker.R

set_picker_ui <- function(id) {
  ns <- NS(id)
  tagList(
    selectInput(
      inputId = ns("set_num"),
      label = "Select a set"
      choices = c(),
      multiple = FALSE
    )
  )
}
  • id: String to use for namespace
  • ns <- NS(id): Create proper namespace function

Anatomy of a Module (Server)

mod_picker.R

set_picker_server <- function(input, output, session, sets_rv) {
  set_choices <- reactive({
    # do something with sets_rv
  })

  observeEvent(set_choices(), {
    req(set_choices())
    updateSelectInput(
      "set_num",
      choices = set_choices()
    )
  })
}

Anatomy of a Module (Server)

mod_picker.R

set_picker_server <- function(id, sets_rv) {
  moduleServer(
    id,
    function(input, output, session) {
      set_choices <- reactive({
        # do something with sets_rv
      })

      observeEvent(set_choices(), {
        req(set_choices())
        updateSelectInput(
          "set_num",
          choices = set_choices()
        )
      })
    }
  )
}

Minimal changes necessary

Anatomy of a Module (Server)

set_picker_server <- function(id, sets_rv) {
  moduleServer(
    id,
    function(input, output, session) {
      set_choices <- reactive({
        # do something with sets_rv
      })

      observeEvent(set_choices(), {
        req(set_choices())
        updateSelectInput(
          "set_num",
          choices = set_choices()
        )
      })
    }
  )
}

🤔 id

moduleServer(): Encapsulate server-side logic with namespace applied.

Invoking Modules

app.R

library(shiny)
library(bslib)
ui <- page_fluid(
  set_picker_ui("mod1")
)

server <- function(input, output, session) {
  sets_rv <- reactive({
    # processing
  })

  set_picker_server("mod1", sets_rv)
}

shinyApp(ui, server)

Giving and Receiving

mod_picker.R

set_picker_ui <- function(id, label = "Select a set") {
  ns <- NS(id)
  tagList(
    selectInput(
      inputId = ns("set_num"),
      label = label,
      choices = c(),
      multiple = FALSE
    )
  )
}
  • Reasonable inputs: static values, vectors, flags
  • Avoid reactive parameters
  • Return value: tagList() of inputs, output placeholders, and other UI elements

Giving and Receiving

mod_picker.R

set_picker_server <- function(id, sets_rv) {
  moduleServer(
    id,
    function(input, output, session) {
      set_choices <- reactive({
        # do something with sets_rv
      })

      observeEvent(set_choices(), {
        req(set_choices())
        updateSelectInput(
          "set_num",
          choices = set_choices()
        )
      })
    }
  )
}

Input & return values can be a mix of static and reactive objects

To () or not to ()

app_server.R

# app server
sets_rv <- reactive({
  # processing
})

set_picker_server("mod1", sets_rv)

mod_picker.R

set_picker_server <- function(id, sets_rv) {
  moduleServer(
    id,
    function(input, output, session) {
      # ...

      set_selection  <- reactive({
        input$set_num
      })

      set_selection
    }
  )
}
  • Reactive parameters reference by name: sets_rv
  • Inside module, invoke reactive parameter as you would any other reactive in Shiny: sets_rv()
  • Any reactive(s) returned by module should also be reference by name: set_selection, set_selection()

Code-Along

Add a new Shiny module to pick LEGO set themes

  • Details
  • Posit Cloud project: Application Structure Code-along 1

Your Turn: Exercise 1

Create a new Shiny module with LEGO data metrics!

  • Details
  • Posit Cloud project: Application Structure Exercise 1
10:00

Dependency Management

Turned Upside-Down

Imagine your application is working great!


update.packages(ask = FALSE)
remotes::install_github("pkg")

Turned Upside-Down

ggplot2 version 0.9.3

ggplot2 version 1.0.0

Take Control with {renv}

Create reproducible environments for your R projects.

  • Next generation of {packrat}
  • Isolated package library from rest of your system
  • Transfer projects to different collaborators / platforms
  • Reproducible package installation
  • Easily create new projects or convert existing projects with RStudio or built-in functions.

Under the Hood

Upon initializing a project:

  1. Project-level .Rprofile to activate custom package library on startup
  2. Lockfile renv.lock to describe state of project library
  3. renv/library to hold private project library
  4. renv/activate.R performs activation

Develop a Routine

Sticking with {renv} will pay off (trust me)

  • Fair play to mix packages from CRAN, GitHub, and proprietary sources
  • Roll back when a package upgrade doesn’t play nicely
  • You make the call when to update your library!