Shiny in Production: App and Database Syncing

August 22, 2020
R shiny DBI Rs

When using shiny in production, often you will want to have some sort of database interactions for storing and accessing data. The DBI package provides an easy way to do the database interactions with a variety of SQL database flavors. In this example, I’m going to use a SQLite in memory database for reproducibility. In practice, you will just switch to the code to use a persistent database. Let’s start by creating a table to right to.

library(RSQLite)
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbExecute(con, 'CREATE TABLE mytable (col1 int, col2 varchar(10));')
## [1] 0

Check that the table exists now.

dbListTables(con)
## [1] "mytable"

Add a single row to the table to check that we can insert.

dbExecute(con, 'INSERT INTO mytable ([col1], [col2]) VALUES (1, "b");"')
## [1] 1
dbGetQuery(con, 'SELECT col1, col2 FROM mytable')
##   col1 col2
## 1    1    b

So now we have a table in the database and we can write to it. When the shiny application loads, we will want to show the user what is in the table.

mytableinshiny <- reactive({
  dbGetQuery(con, 'SELECT col1, col2 from mytable')
})
mytableinshiny()
##   col1 col2
## 1    1    b

This works great and now we can use the data from the database to do whatever we need it to do in the shiny application. If we create the following action, the function does the necessary side effect of writing to the database but unless the user reloads the application mytableinshiny does not update in the application.

observeEvent(input$writetodb, {
  sql <- sqlInterpolate(con, 'INSERT INTO mytable ([col1], [col2]) VALUES (col1, ?col2);"',
                        col1 = input$col1, col2 = input$col2)
  dbExecute(con, sql)
})
mytableinshiny()
##   col1 col2
## 1    1    b

After the database insertion, we need a way to inform shiny to update the reactive data.frame. We can use a reactive trigger to “push” a button that lets shiny know that the database has changed and it should re-execute the SELECT query.

makereactivetrigger <- function() {
  rv <- reactiveValues(a = 0)
  list(
    depend = function() {
      rv$a
      invisible()
    },
    trigger = function() {
      rv$a <- isolate(rv$a + 1)
    }
  )
}
dbtrigger <- makereactivetrigger()

The trigger is now an object that has a couple of functions to pass messages around. We need to set the reactive value to re-execute so add the depend function and after the database interaction executes we add the trigger function. Also, it’s good practice to use sqlInterpolate to help prevent sql injection attacks.

mytableinshiny <- reactive({
  dbtrigger$depend()
  dbGetQuery(con, 'SELECT col1, col2 from mytable')
})
observeEvent(input$writetodb, {
  sql <- sqlInterpolate(con, 'INSERT INTO mytable ([col1], [col2]) VALUES (?col1, ?col2)',
                        col1 = input$col1, col2 = input$col2)
  dbExecute(con, sql)
  dbtrigger$trigger()
})

Within the app, we put the data in the shiny input forms and hit the write to database action button when ready.

mytableinshiny()
##   col1 col2
## 1    1    b
## 2    2    a

Now when the database is updated the shiny application syncs to what is in the database after the insertion.

Here’s a toy example to play with:

library(shiny)
library(RSQLite)
library(DBI)
makereactivetrigger <- function() {
    rv <- reactiveValues(a = 0)
    list(
        depend = function() {
            rv$a
            invisible()
        },
        trigger = function() {
            rv$a <- isolate(rv$a + 1)
        }
    )
}
dbtrigger <- makereactivetrigger()
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbExecute(con, 'CREATE TABLE mytable (col1 int, col2 varchar(10));')
ui <- fluidPage(
    numericInput('col1', 'col1', value = 1L, step = 1L),
    textInput('col2', 'col2', value = 'a'),
    actionButton('writetodb', 'Save'),
    tableOutput('dbtable')
)
server <- function(input, output) {
    mytableinshiny <- reactive({
        dbtrigger$depend()
        dbGetQuery(con, 'SELECT col1, col2 from mytable')
    })
    observeEvent(input$writetodb, {
        sql <- sqlInterpolate(con, 'INSERT INTO mytable ([col1], [col2]) VALUES (?col1, ?col2)',
                              col1 = input$col1, col2 = input$col2)
        dbExecute(con, sql)
        dbtrigger$trigger()
    })
    output$dbtable <- renderTable({
        mytableinshiny()
    })
}
shinyApp(ui = ui, server = server)

Shiny: Add/Removing Modules Dynamically

February 11, 2020
R shiny data science

SQL Server Schemas & R Tip

October 19, 2019
DBI odbc SQL Server R

Setting Up Raspberry Pi Temperature/Humidity Sensors for Data Analysis in R

February 21, 2019
R DBI ggplot2 RMariaDB Raspberry Pi bash MYSQL