A Guide to Creating a Basic R Shiny App from a GitHub README (Markdown)

Gigi Kenneth
Dev Genius
Published in
6 min readDec 28, 2023

--

This was a product of boredom and curiosity, and I’m unsure if it made any sense to do this, but it’s done. Sort of.

Welcome to this step-by-step guide where we’ll transform a Markdown file (README.md) on GitHub into a simple interactive web application using R Shiny.

A bit of background, earlier this year, I worked on an AI Ethics and Policies centered project, and in the process of putting it together, I came across a lot of useful resources, so I put them all in a public GitHub repository. I update this almost regularly.

AI Ethics Resources

I randomly thought to myself, hmmm 🤔, can I turn this repo into a Shiny app for no reason? 🤨

Well…

Let’s break it down into simple, probably easy-to-follow steps.

There are best practices for writing technical blogs, and I’m not following any of them because…because. 😬

Let’s do it!

Step 1: Setting Up Your R Environment

Install R and RStudio

  1. Download and Install R: Visit the CRAN website and download R for your operating system.
  2. Download and Install RStudio: Go to the RStudio download page and install RStudio, a user-friendly interface for R.

Install Required Packages

  1. Open RStudio: Launch RStudio on your computer.
  2. Install Packages: In the RStudio console, type and run the following commands to install the necessary packages:
install.packages("shiny")
install.packages("httr")
install.packages("stringr")

Step 2: Create a New Shiny Project

  1. Start a New Project: In RStudio, go to File > New File > Shiny Web Application.
  2. Name Your Project: Enter a name for your app, like “GitHubMarkdownViewer”, and choose where to save it. For me, it’s “aiethe3”, and I’m too lazy to change it because there were several iterations and I threw c̶a̶u̶t̶i̶o̶n̶ best practices to the wind. 😅

Step 3: Writing the App Code

Now, let’s break down the provided code into understandable parts.

1. Load Libraries

First, load the Shiny, httr, and stringr libraries. This is necessary to use the functions they provide.

library(shiny)
library(httr)
library(stringr)

This section loads three essential libraries:

  • shiny: For building the interactive web app.
  • httr: For making HTTP requests (here, to fetch the README file from GitHub).
  • stringr: For string manipulation, useful in parsing text.

2. Fetching README from GitHub

The fetchReadme function fetches the README.md file from a specified GitHub repository.

fetchReadme <- function() {
url <- "https://raw.githubusercontent.com/gigikenneth/ai-ethics-resources/main/README.md"
res <- GET(url)
content <- content(res, "text")
return(content)
}
  • fetchReadme: A custom function defined to fetch the README file.
  • It constructs a URL pointing to the raw README.md file on GitHub.
  • GET(url) fetches the file, and content(res, "text") extracts its text content.

3. Parsing Markdown Content

parseMarkdown splits the markdown content into sections for easy display. Can we discuss the regex another day?

parseMarkdown <- function(mdContent) {
sections <- unlist(str_split(mdContent, "\n## "))
parsedSections <- lapply(sections[-1], function(section) {
header <- str_extract(section, "^[^\n]+")
bullets <- str_match_all(section, "- (.*)")[[1]][,2]
bullets <- sapply(bullets, markdownToHTML, USE.NAMES = FALSE)
list(header = header, bullets = bullets)
})
return(parsedSections)
}
  • Splits the markdown into sections based on headers (denoted by ##).
  • For each section, it extracts the header and bullet points.
  • Converts markdown links in bullet points to HTML using markdownToHTML.

4. Converting Markdown Links to HTML

markdownToHTML converts markdown links to HTML format, making them clickable in the web app.

markdownToHTML <- function(text) {
return(gsub("\\[([^]]+)\\]\\(([^)]+)\\)", "<a href='\\2' target='_blank'>\\1</a>", text))
}
  • A helper function that transforms markdown link syntax to HTML hyperlinks.

5. User Interface Design

The ui object uses fluidPage to create a flexible layout. It includes styling for tabs and a title panel.

ui <- fluidPage(
...
)
  • Defines the layout and style of the app’s user interface.
  • Uses fluidPage for a flexible layout.
  • The style within tags$style(HTML("...")) customizes the appearance of tabs.
ui <- fluidPage(
tags$head(
tags$style(HTML("
.nav-tabs .nav-link {
color: blue !important; /* Tab text color */
font-weight: bold !important; /* Make tab titles bold */
}
.nav-tabs .nav-link.active {
background-color: #5b5ba6 !important; /* Active tab color */
border-color: #5b5ba6 !important; /* Border color for active tab */
}
.nav-tabs .nav-link:hover {
background-color: #6d6dbf !important; /* Hover state color */
}
"))
)

6. Server Function

The server function handles the back-end operations of the app:

  • Fetching and Parsing Data: It uses the fetchReadme and parseMarkdown functions to get and process the GitHub markdown.
  • Dynamic UI: Uses renderUI to create tabs dynamically for each section of the markdown file.
server <- function(input, output, session) {
...
}
  • The core logic of the Shiny app.
  • markdownContent is a reactive value, updated with the latest README content.
  • observe block fetches and parses the markdown file every 5 minutes.
  • output$tabsUI dynamically creates tabs in the UI based on the parsed markdown.
server <- function(input, output, session) {
markdownContent <- reactiveVal(list())

observe({
content <- fetchReadme()
parsedContent <- parseMarkdown(content)
markdownContent(parsedContent)
invalidateLater(300000, session) # Refresh every 5 minutes
})

output$tabsUI <- renderUI({
content <- markdownContent()
if (is.null(content)) return(NULL)

tabs <- lapply(content, function(section) {
tabPanel(title = section$header,
HTML(paste("<ul><li>", paste(section$bullets, collapse = "</li><li>"), "</li></ul>")))
})

do.call(navbarPage, c("AI Ethics Resources", id = "nav", tabs))
})
}

7. Running the App

The shinyApp(ui, server) call at the end of the script is what makes the app run.

shinyApp(ui, server)
  • This command launches the Shiny app, combining the UI and server components.

8. Run Your App

Click the ‘Run App’ button in RStudio, and you’re done.

AI Ethics Resources Shiny App

9. Debugging

If the app doesn’t work:

Let Me Tell You Something
  1. Check for Errors: Look in the RStudio console for any error messages.
  2. Verify URLs: Ensure the GitHub URL in fetchReadme is correct and accessible.

10. Customization and Improvement

Feel free to customize the code (not sure why you’d want to try this, but okay). For example, change the GitHub URL to point to a different markdown file or modify the UI styling to fit your preferences. You could add a search bar (or maybe I’m delusional) or choose to deploy it; here’s a blog post about that.

Here’s the full code:

library(shiny)
library(httr)
library(stringr)

# Function to fetch README.md from GitHub
fetchReadme <- function() {
url <- "https://raw.githubusercontent.com/gigikenneth/ai-ethics-resources/main/README.md"
res <- GET(url)
content <- content(res, "text")
return(content)
}

# Function to parse markdown content
parseMarkdown <- function(mdContent) {
sections <- unlist(str_split(mdContent, "\n## "))

parsedSections <- lapply(sections[-1], function(section) {
header <- str_extract(section, "^[^\n]+")

# Extract bullet points and convert markdown links to HTML
bullets <- str_match_all(section, "- (.*)")[[1]][,2]
bullets <- sapply(bullets, function(bullet) {
markdownToHTML(bullet)
}, USE.NAMES = FALSE)

list(header = header, bullets = bullets)
})

return(parsedSections)
}

# Function to convert markdown links to HTML
markdownToHTML <- function(text) {
return(gsub("\\[([^]]+)\\]\\(([^)]+)\\)", "<a href='\\2' target='_blank'>\\1</a>", text))
}

ui <- fluidPage(
titlePanel("AI Ethics Resources"),
uiOutput("tabsUI")
)

server <- function(input, output, session) {
markdownContent <- reactiveVal(list())

observe({
content <- fetchReadme()
parsedContent <- parseMarkdown(content)
markdownContent(parsedContent)
invalidateLater(300000, session) # Refresh every 5 minutes
})

output$tabsUI <- renderUI({
content <- markdownContent()
if (is.null(content)) return(NULL)

tabs <- lapply(content, function(section) {
tabPanel(title = section$header,
HTML(paste("<ul><li>", paste(section$bullets, collapse = "</li><li>"), "</li></ul>")))
})

do.call(navbarPage, c("AI Ethics Resources", id = "nav", tabs))
})
}

shinyApp(ui, server)

Summary

Congratulations! You have created an interactive R Shiny app that fetches and displays a README.md file from GitHub.

Keep experimenting and exploring the vast capabilities of R and Shiny! See you in my next blog. 👋🏾

I hope you can hear this meme. ❤️

--

--