Skip to contents

Shiny-style authoring, stateless API deployment.

aurora lets you build a web app the way you’d build a Shiny app — author the UI in R with bslib, scaffold the project, run it locally with hot-reload — but it compiles the UI to a static index.html at build time and lets plumber2 serve that UI plus your JSON API. There’s no reactive server, no per-user R process, and no sticky sessions: the app is stateless and scales horizontally behind a load balancer, ShinyProxy, or plain Docker.

  • 🎨 UI in R — author with bslib + _brand.yml; ship a static HTML asset.
  • Stateless — plumber2 serves the assets and JSON routes; scale by adding containers.
  • 🧩 Convention over config — fixed folders, no required manifest.
  • 🔐 Batteries, opt-in — JWT-cookie auth, a hot-reloading data store, OpenTelemetry.
  • 🐳 One-command deploy — generate a Dockerfile (debian or alpine) or a ShinyProxy spec.

What an app looks like

A route handler is just an annotated function — its URL is the annotation:

# routers/iniciativas.R   ->   GET /api/iniciativas/data
#* @get /api/iniciativas/data
#* @serializer json
function() {
  dados <- aurora_data_get(store, "iniciativas")   # hot-reloaded from disk
  list(data = dados, municipios = aurora_unique(dados$municipio))
}

The UI is plain bslib, compiled once to www/index.html:

# build_ui.R   ->   www/index.html (static, built by aurora_build_ui())
build_ui <- function() {
  bslib::page_fillable(
    theme = bslib::bs_theme(version = 5, brand = TRUE),
    aurora_component("/api/iniciativas/data", id = "tabela")  # wires a div to the endpoint
  )
}
build_ui.R (bslib + _brand.yml) ──aurora_build_ui()──▶ www/index.html (static)

Browser ◀──▶ plumber2 server (aurora_app)
              ├── serves www/ (static UI + core.js/app.js)
              ├── routers/*.R        → JSON
              ├── helpers/*.R         (sourced first: config, db, auth, store)
              └── optional JWT-cookie auth guard

Installation

# From GitHub — latest release (recommended):
# install.packages("pak")
pak::pak("aurora-govpe/aurora-rpkg@v0.1.5")

# Development version (main branch):
pak::pak("aurora-govpe/aurora-rpkg")

# From CRAN (once published):
install.packages("aurora")

remotes/devtools work too: remotes::install_github("aurora-govpe/aurora-rpkg@v0.1.5").

Quick start

library(aurora)

aurora_create_app("meu_app", template = "minimal")
aurora_add_route("iniciativas", dir = "meu_app")   # -> routers/iniciativas.R
aurora_run("meu_app")                              # http://127.0.0.1:8000 (docs at /__docs__)

aurora_run("meu_app", watch = TRUE) rebuilds the static UI on change. Theming is a _brand.yml consumed by bslib; auth, data, and telemetry are opt-in (template = "auth", aurora_data_store(), aurora_run(otel = TRUE)).

Canonical app layout (convention; no manifest required)

<app>/
├── api.R            # entry: aurora::aurora_run("."), host/port from env
├── build_ui.R       # defines build_ui() -> htmltools tag; sources ui_modules/
├── helpers/         # *.R sourced before routers are parsed (config, db, auth…)
├── routers/         # plumber2 annotated handlers; URL = annotation path
├── ui_modules/      # ui_*.R partials
├── www/             # static assets: js/core.js (runtime), js/app.js, style.css, images/
├── data/config.yml  # app config (config package)
├── _brand.yml       # optional: bslib theming (color/type/logo)
├── _aurora.yml      # optional: overrides name/engine/auth/packages/attach/statics
└── Dockerfile       # generated by aurora_dockerfile()

Deploy

aurora_dockerfile("meu_app")                        # flavor = "debian" (default)
aurora_dockerfile("meu_app", flavor = "alpine")     # tiny image, source-built
aurora_build_image("meu_app", tag = "org/meu_app:latest", push = TRUE)

The container serves the prebuilt static UI, so neither flavor installs bslib/shiny at runtime. aurora_build_image() targets linux/amd64 by default, so images built on an Apple Silicon Mac run on x86-64 servers (pass platform = NULL for a host-native build).

debian (default) — rocker/r-ver alpinerhub/r-minimal
R packages prebuilt binaries (Posit Package Manager) compiled from source (installr)
Build speed fast slow (full compile)
Image size large (~1 GB+) small
Architectures amd64 binaries (arm64 compiles) builds natively on amd64 and arm64
System deps apt, broad/easy (glibc) curated apk, must compile (musl)

Rule of thumb: debian for fast CI and rich/geo dependencies; alpine when image size matters most and you can absorb longer builds.

Apps that share static assets (logo, JS libraries, CSS) can serve them from one server-side directory via _aurora.yml:

# _aurora.yml — serve /srv/aurora-shared at /assets (alongside www/ at /)
statics:
  /assets: /srv/aurora-shared

Mount the directory as a volume (-v /srv/aurora-shared:/srv/aurora-shared:ro) and every app picks up changes from one place. See vignette("deploy").

Core functions

Function Purpose
aurora_create_app() Scaffold the canonical layout (minimal / auth template)
aurora_add_route() Generate an annotated plumber2 router in routers/
aurora_build_ui() / aurora_app() / aurora_run() Compile UI, assemble the API, run locally (incl. attach= to load _aurora.yml packages and on_exit= for shutdown cleanup)
aurora_config() Read data/config.yml anchored to the app root (no cwd pitfall)
aurora_check() Lint the app: UI code in runtime helpers, undeclared packages, missing UI
aurora_component() Emit a UI element wired to a JSON endpoint (data-endpoint)
aurora_unbox() / aurora_geojson() / aurora_unique() NULL-safe JSON response helpers (unbox / sf→GeoJSON / sorted-unique)
aurora_data_store() Globals-free, hot-reloading data store for handlers
aurora_auth_jwt() + aurora_jwt_* / aurora_*_auth_cookie() JWT-cookie auth scheme (pluggable)
aurora_dockerfile() / aurora_build_image() / aurora_shinyproxy_yaml() Deploy: Docker + ShinyProxy

See the Get started article, the Gallery, and the function reference.

Design decisions

  • plumber2 only. Each handler’s URL comes from its annotation.
  • Thin JS runtime. core.js is aurora’s basics; app.js and aurora_component() keep rendering in the app, not the package.
  • Pluggable auth. A JWT-cookie @header guard, never in aurora_app()’s core path.
  • Convention over manifest. Fixed folders; _aurora.yml optional.
  • Theming via brand.yml. bslib consumes _brand.yml at build time.

Authors

  • Andre Leite — maintainer ()
  • Marcos Wasilew · Hugo Vasconcelos · Carlos Amorin · Diogo Bezerra · Júlia Nascimento Barreto

Developed by the NGR-Dados team (SEPLAG-PE) with UFPE.

License

MIT © aurora authors. Contributions welcome via issues and pull requests.