9  Shiny

Shiny is a framework for building web applications in R. It connects a browser-based UI to server-side R code through reactive programming.

9.1 How Shiny works

A Shiny app has two parts:

  • UI function: Defines the HTML elements rendered in the browser
  • Server function: Contains the R logic that responds to user interactions
ui <- function(request) {
  shiny::tagList(
    shiny::actionButton("show", "Click"),
    shiny::uiOutput("message")
  )
}

server <- function(input, output, session) {
  shiny::observeEvent(input$show, {
    shiny::showNotification("You pushed that.")
    output$message <- shiny::renderUI({
      shiny::tags$h1("You have clicked me!")
    })
  })
}

shiny::shinyApp(ui, server)

Before the click, the browser has:

<button id="show" class="btn btn-default action-button">Click</button>
<div id="message" class="shiny-html-output"></div>

After the click, the server populates the message div:

<div id="message" class="shiny-html-output">
  <h1>You have clicked me!</h1>
</div>

The key insight: Shiny uses id attributes to create a communication channel between the browser and the R server. The id on a UI element corresponds to input$id (for inputs) or output$id (for outputs) on the server side.

9.2 Modules

Modules encapsulate a piece of UI + server logic into a reusable unit. Every ndexr component is a module.

ui_button <- function(id = "button") {
  ns <- shiny::NS(id)
  shiny::tagList(
    shiny::actionButton(ns("show"), "Click"),
    shiny::uiOutput(ns("message"))
  )
}

server_button <- function(id = "button") {
  shiny::moduleServer(id, function(input, output, session) {
    shiny::observeEvent(input$show, {
      output$message <- shiny::renderUI({
        shiny::tags$h1("You have clicked me!")
      })
    })
  })
}

# Use in your app:
ui <- function(request) { ui_button("my_button") }
server <- function(input, output, session) { server_button("my_button") }
shiny::shinyApp(ui, server)

The NS() function namespaces all IDs. The button’s id becomes my_button-show in the browser, not just show. This prevents collisions when you use multiple instances of the same module.

On the server side, moduleServer handles the namespacing automatically — you write input$show, not input$my_button-show.

9.3 ID and class conventions

In ndexr:

  • id is for action — creating a channel between UI and server
  • class is for styling — Bootstrap CSS classes control appearance

IDs must be unique. Classes can be shared across elements. Shiny also uses classes internally for reactivity (e.g., action-button on actionButton).

9.4 Nested modules

Modules can contain other modules. This is how ndexr builds complex interfaces from simple pieces:

ui_table <- function(id = "table") {
  ns <- shiny::NS(id)
  shiny::tags$table(
    class = "table",
    shiny::tags$thead(
      shiny::tags$tr(
        lapply(colnames(datasets::mtcars), function(x) {
          shiny::tags$th(scope = "col", x)
        })
      )
    ),
    shiny::uiOutput(ns("rows"), container = shiny::tags$tbody)
  )
}

server_table <- function(id = "table") {
  shiny::moduleServer(id, function(input, output, session) {
    ns <- session$ns
    output$rows <- shiny::renderUI({
      lapply(split(datasets::mtcars, seq_len(nrow(datasets::mtcars))), function(row) {
        row_id <- paste0("row_", rownames(row))
        server_row(row_id, row)
        ui_row(ns(row_id))
      })
    })
  })
}

Each row is its own module with its own namespace. The table module creates and manages them dynamically.

9.5 The ndexr project template

The default Shiny project for ndexr servers lives in ~/public/services/console/:

~/public/
├── docker-compose.yaml
├── Makefile
└── services/
    └── console/
        ├── app.r
        ├── Dockerfile
        ├── renv.lock
        └── src/
            └── (your application code)
  • docker-compose.yaml defines the container and port mapping
  • Makefile provides build/scale/stop commands
  • Dockerfile specifies the system and R dependencies
  • renv.lock pins R package versions for reproducibility
  • src/ contains your Shiny application

Run make up profile=ndexrio scale=10 from ~/public/ to build and launch.