| Title: | Build Stateless Web Apps with 'plumber2' |
|---|---|
| Description: | A scaffolding and deployment toolkit for building stateless web applications in R on top of the 'plumber2' web framework (<https://plumber2.posit.co/>). The UI is authored with 'bslib' and compiled to a static HTML asset at build time, while 'plumber2' serves the assets and exposes JSON API routes. Provides functions to scaffold app skeletons, run them locally, and generate Dockerfiles and images suitable for 'ShinyProxy' or plain Docker. |
| Authors: | Andre Leite [aut, cre], Marcos Wasilew [aut], Hugo Vasconcelos [aut], Carlos Amorin [aut], Diogo Bezerra [aut], Júlia Nascimento Barreto [aut] |
| Maintainer: | Andre Leite <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.12 |
| Built: | 2026-06-24 13:23:20 UTC |
| Source: | https://github.com/cran/aurora |
Generates an annotated plumber2 route file under routers/. Because
aurora_app() parses every file in routers/, no manifest update is needed.
The handler's URL embeds the mount prefix directly in its annotation, so
there is no runtime path injection.
aurora_add_route(name, mount = NULL, dir = ".")aurora_add_route(name, mount = NULL, dir = ".")
name |
Route name; becomes |
mount |
URL prefix for the route. Defaults to |
dir |
App directory (canonical aurora layout). |
The created route file path, invisibly.
Convention-based assembly: builds the UI (optional), sources helpers/*.R,
parses every routers/*.R into a plumber2 API, and serves the www/
directory at /. No _aurora.yml is required; an optional manifest only
overrides a few keys.
aurora_app( dir = ".", rebuild_ui = TRUE, host = "127.0.0.1", port = 8000L, otel = NULL, verbose = NULL, attach = NULL )aurora_app( dir = ".", rebuild_ui = TRUE, host = "127.0.0.1", port = 8000L, otel = NULL, verbose = NULL, attach = NULL )
dir |
App directory (canonical aurora layout). |
rebuild_ui |
Whether to rebuild the static UI before assembling. |
host, port
|
Bind address/port baked into the API object. Usually
overridden by |
otel |
Wire OpenTelemetry logging ( |
verbose |
Emit a per-step cli log (one line per sourced helper /
parsed router, otel wiring). |
attach |
Attach the runtime packages declared in |
Extra static mounts can be declared in _aurora.yml under statics: – a map
of URL prefix to directory, served at that prefix (in addition to www/ at
/). This is for assets shared across apps from a server-side directory
mounted as a volume; e.g. statics: then /assets: /srv/shared serves
/srv/shared/logo.png at /assets/logo.png. Relative paths resolve against
the app root; a missing directory (e.g. an unmounted volume) is skipped with
a warning so the app still starts. See ADR-018.
Each handler's URL is taken verbatim from its #* @get /... annotation, so
the route prefix is embedded by aurora_add_route() at scaffold time – there
is no runtime path injection here.
An object of class aurora_app.
aurora's auth is pluggable and never baked into aurora_app()'s core path.
This is the one provided scheme: a stateless JSON Web Token
signed with jose (HMAC) and delivered as an HttpOnly cookie. It is the
plumber2 translation of the reference app's v1 @filter JWT scheme.
aurora_auth_jwt( secret = Sys.getenv("AURORA_JWT_SECRET"), cookie = "token", expiry = 28800L )aurora_auth_jwt( secret = Sys.getenv("AURORA_JWT_SECRET"), cookie = "token", expiry = 28800L )
secret |
Secret used to sign/verify tokens (string or raw). Prefer supplying it via an environment variable rather than hardcoding. |
cookie |
Name of the cookie carrying the token. |
expiry |
Token lifetime in seconds. |
The companion helpers operate on the scheme object:
aurora_jwt_token() mints a signed token (at login).
aurora_set_auth_cookie() / aurora_clear_auth_cookie() manage the cookie.
aurora_jwt_guard() is the gate: call it from a @header handler on
/api/* and it rejects unauthenticated requests with a 401.
Auth is wired entirely in your app's annotated router files (a @header
guard + public /auth/* routes), so aurora_app() needs no auth knowledge.
See the auth template (aurora_create_app()).
An object of class aurora_auth_jwt.
auth <- aurora_auth_jwt(secret = "dev-only-secret") tok <- aurora_jwt_token(auth, list(user = "alice")) aurora_jwt_decode(auth, tok)$userauth <- aurora_auth_jwt(secret = "dev-only-secret") tok <- aurora_jwt_token(auth, list(user = "alice")) aurora_jwt_decode(auth, tok)$user
Thin wrapper around the docker CLI. Requires a Dockerfile (see
aurora_dockerfile()) and a working Docker installation on PATH.
aurora_build_image( dir = ".", tag, push = FALSE, dockerfile = "Dockerfile", platform = "linux/amd64" )aurora_build_image( dir = ".", tag, push = FALSE, dockerfile = "Dockerfile", platform = "linux/amd64" )
dir |
App directory (build context). |
tag |
Image tag, e.g. |
push |
Whether to |
dockerfile |
Path to the Dockerfile relative to |
platform |
Target platform passed to |
The image tag, invisibly.
Sources the app's build_ui.R (root), which must define a build_ui()
function returning an htmltools tag, and writes the rendered HTML to
www/index.html via htmltools::save_html(). Author the layout with
bslib; ship static HTML.
aurora_build_ui(dir = ".")aurora_build_ui(dir = ".")
dir |
App directory (canonical aurora layout). |
The output file path, invisibly.
Static "doctor" for the canonical layout. Reports issues that otherwise surface late (often only when building or running the container):
aurora_check(dir = ".")aurora_check(dir = ".")
dir |
App directory (canonical aurora layout). |
UI code in runtime helpers – shiny/bslib/htmltools
usage in helpers/ (sourced at request time) pulls the UI stack into the
runtime image. UI code belongs in build_ui.R/ui_modules/ (build time).
Undeclared packages – packages referenced in routers//helpers/ but
absent from _aurora.yml packages: (when that list is present), so the
image would miss them.
Missing prebuilt UI – no www/index.html (the container serves it and
does not rebuild).
Invisibly, a data frame of findings (level, message); also printed
via cli.
A thin markup helper: emits an htmltools element carrying its API
endpoint as a data-endpoint attribute (plus any extra attributes you pass).
Your app's feature JavaScript reads element.dataset.endpoint, fetches it
with the window.aurora runtime (aurora.json(...)), and renders however it
likes.
aurora_component(endpoint, ..., id = NULL, tag = "div")aurora_component(endpoint, ..., id = NULL, tag = "div")
endpoint |
API path the feature JS will fetch, e.g. |
... |
Passed to the underlying htmltools tag. Named arguments
become attributes (e.g. |
id |
Element id, so your JS can find it ( |
tag |
HTML tag name to emit. Defaults to |
aurora deliberately ships no rendering JavaScript: this keeps the runtime thin
and leaves charts, tables, and maps fully under the app's control. Use it to avoid
hand-writing the data-* plumbing on every element.
An htmltools tag.
aurora_component("api/sales/data", id = "vendas", style = "height:360px;")aurora_component("api/sales/data", id = "vendas", style = "height:360px;")
data/config.yml, anchored to the app rootThin wrapper over config::get() that resolves data/config.yml relative to
the app directory (an absolute path), instead of the config package's
default search from the current working directory. This avoids the cwd pitfall
where a helper or handler is evaluated with a working directory other than the
app root and config::get() cannot find the file.
aurora_config( value = NULL, ..., dir = ".", file = NULL, config = Sys.getenv("R_CONFIG_ACTIVE", "default") )aurora_config( value = NULL, ..., dir = ".", file = NULL, config = Sys.getenv("R_CONFIG_ACTIVE", "default") )
value |
Config value to read; |
... |
Passed to |
dir |
App directory (used to locate |
file |
Explicit path to the config file; overrides |
config |
Active configuration name; defaults to the |
data/config.yml (app runtime config: DB credentials, environment profiles,
service URLs) is intentionally separate from _aurora.yml (aurora wiring); see
the project decision records. This helper just makes reading it robust.
The requested config value (or the whole config list).
Creates the canonical aurora layout from a bundled template: api.R,
build_ui.R, helpers/, routers/, ui_modules/, www/ (with style.css
and js/app.js), data/config.yml, and .dockerignore. The JS runtime is
copied fresh into www/js/core.js. A first UI build is attempted so the app
is immediately runnable.
aurora_create_app( path, template = c("minimal", "dashboard", "auth"), engine = "plumber2" )aurora_create_app( path, template = c("minimal", "dashboard", "auth"), engine = "plumber2" )
path |
Directory to create. Must not exist or must be empty. |
template |
Bundled template: |
engine |
Web engine. Only |
The app path, invisibly.
Returns the named dataset, re-reading from disk if its modification time has advanced since the last read (hot reload), otherwise returning the cached value.
aurora_data_get(store, name)aurora_data_get(store, name)
store |
|
name |
Registered dataset name. |
The dataset as returned by its reader.
Names of the datasets registered in a store
aurora_data_names(store)aurora_data_names(store)
store |
A character vector of dataset names.
Register a dataset in a data store
aurora_data_register(store, name, path, reader = NULL)aurora_data_register(store, name, path, reader = NULL)
store |
|
name |
Dataset name used in |
path |
File path (resolved against the store's |
reader |
Optional reader function |
The store, invisibly.
Registers named datasets backed by files on disk and hands them to route
handlers without globals or <<-. Each aurora_data_get() checks the file's
modification time and transparently re-reads it if an external process (e.g.
an ETL job) has rewritten it – the stateless equivalent of the reference
app's carregar_bases() mtime trick.
aurora_data_store(..., dir = ".", readers = list())aurora_data_store(..., dir = ".", readers = list())
... |
Named file paths to register (e.g. |
dir |
Base directory that relative dataset paths are resolved against.
Resolved to an absolute path once, when the store is created (not at read
time), so later changes to the working directory cannot break reads. Defaults
to |
readers |
Named list of reader functions keyed by lowercase file
extension, merged over (and overriding) the built-ins ( |
Define the store once in a helpers/*.R file (sourced before routers are
parsed) and read from it in handlers:
# helpers/data.R store <- aurora_data_store(sales = "data/sales.rds", dir = ".") # routers/sales.R #* @get /api/sales #* @serializer json function() aurora_data_get(store, "sales")
An object of class aurora_data_store.
f <- tempfile(fileext = ".rds") saveRDS(data.frame(x = 1:3), f) store <- aurora_data_store(demo = f) aurora_data_get(store, "demo")f <- tempfile(fileext = ".rds") saveRDS(data.frame(x = 1:3), f) store <- aurora_data_store(demo = f) aurora_data_get(store, "demo")
Writes a Dockerfile that installs system deps, R packages (incl. plumber2
and aurora), copies the app, and runs api.R. The image suits plain
Docker or a ShinyProxy container. A .dockerignore is written if absent.
aurora_dockerfile( dir = ".", flavor = c("debian", "alpine"), base = NULL, sysdeps = "auto", port = 8000L, aurora_source = "aurora-govpe/[email protected]", tz = "America/Recife", locale = "pt_BR.UTF-8", write = TRUE )aurora_dockerfile( dir = ".", flavor = c("debian", "alpine"), base = NULL, sysdeps = "auto", port = 8000L, aurora_source = "aurora-govpe/[email protected]", tz = "America/Recife", locale = "pt_BR.UTF-8", write = TRUE )
dir |
App directory. |
flavor |
Base-image flavor: |
base |
Base Docker image. |
sysdeps |
|
port |
Port exposed and bound (via the |
aurora_source |
pak/remotes spec used to install aurora in the image.
Defaults to the pinned release |
tz |
Timezone baked into the image as |
locale |
Locale baked as |
write |
Write the file ( |
Two flavors:
"debian" (default) – rocker/r-ver; installs R packages as binaries
from Posit Package Manager (fast builds; amd64). Best for heavy/geo apps and
fast CI. Larger image.
"alpine" – rhub/r-minimal; a tiny image (~25 MB base) that compiles
every package from source via installr (no CRAN binaries; builds are
slower). Best when image size matters or you need native arm64. Tune the
Alpine build/runtime system deps via sysdeps if your packages need more.
Runtime R packages are detected by scanning routers/, helpers/, and
api.R. To pin them explicitly (reproducible images), set a packages: list
in _aurora.yml; plumber2 and aurora are always added.
The Dockerfile path (if written) or its contents, invisibly.
Returns an unboxed GeoJSON string for an sf object, or NULL if the
input is NULL or not an sf object (so an absent layer serializes as JSON
null, not [] or an error).
aurora_geojson(x)aurora_geojson(x)
x |
An sf object, or |
An unboxed GeoJSON string, or NULL.
Returns the payload if the signature is valid and the token has not expired;
otherwise NULL (never throws).
aurora_jwt_decode(auth, token)aurora_jwt_decode(auth, token)
auth |
An |
token |
The token string (e.g. |
The decoded payload list, or NULL.
Reads the scheme's cookie from request, verifies it, and – if invalid or
absent – stops handling with a 401 Unauthorized via
reqres::abort_unauthorized(). On success it returns the payload invisibly.
Use it inside a @header handler on /api/*, returning plumber2::Next to
continue:
aurora_jwt_guard(auth, request)aurora_jwt_guard(auth, request)
auth |
An |
request |
The reqres request object (the |
#* @any /api/*
#* @header
function(request) {
aurora_jwt_guard(auth, request)
plumber2::Next
}
The decoded payload, invisibly (or aborts).
Signs claims (plus an exp expiry computed from the scheme) with HMAC.
aurora_jwt_token(auth, claims = list())aurora_jwt_token(auth, claims = list())
auth |
An |
claims |
A named list of claims to embed (e.g. |
The signed token as a string.
Rebuilds the UI (optional), assembles the aurora_app() from convention, and
starts the plumber2 server. Development-time equivalent of
shiny::runApp(). The generated api.R calls this function, so local dev
and container entry share one assembly path.
aurora_run( dir = ".", port = 8000L, host = "127.0.0.1", rebuild_ui = NULL, watch = FALSE, watch_interval = 1, otel = NULL, verbose = NULL, attach = NULL, on_exit = NULL )aurora_run( dir = ".", port = 8000L, host = "127.0.0.1", rebuild_ui = NULL, watch = FALSE, watch_interval = 1, otel = NULL, verbose = NULL, attach = NULL, on_exit = NULL )
dir |
App directory (canonical aurora layout). |
port |
Port to bind. |
host |
Host/interface to bind. |
rebuild_ui |
Whether to rebuild the static UI before running. |
watch |
Live-reload for development. When |
watch_interval |
Polling interval in seconds when |
otel |
Enable OpenTelemetry logging. Passed to |
verbose |
Per-step cli logging. Passed to |
attach |
Attach the |
on_exit |
Optional cleanup function |
The result of plumber2::api_run(), invisibly.
Generates the YAML spec entry that goes under proxy.specs in a
Ruscker configuration,
pointing at a built aurora image (see aurora_build_image()). Ruscker is a
reverse proxy and container orchestrator (a lightweight ShinyProxy
alternative) that reads the ShinyProxy application.yml schema and adds its
own fields. An aurora app is a stateless 'plumber2' API, so this emits a
type: api spec – Ruscker load-balances a replica pool of the container
instead of running one container per session (contrast
aurora_shinyproxy_yaml(), which emits the interactive Docker-backed spec).
aurora_ruscker_yaml( image, dir = ".", id = NULL, display_name = NULL, description = NULL, port = 8000L, docs_path = "/__docs__", health_path = "/__healthz__", rate_limit = NULL, cors = NULL, min_replicas = 0L, max_replicas = 3L, env = NULL, wrap = FALSE, write = FALSE, file = NULL )aurora_ruscker_yaml( image, dir = ".", id = NULL, display_name = NULL, description = NULL, port = 8000L, docs_path = "/__docs__", health_path = "/__healthz__", rate_limit = NULL, cors = NULL, min_replicas = 0L, max_replicas = 3L, env = NULL, wrap = FALSE, write = FALSE, file = NULL )
image |
Container image tag, e.g. |
dir |
App directory, used only to default |
id |
Spec id. Defaults to the app name. |
display_name |
Human-facing name. Defaults to the app name. |
description |
Optional one-line description. |
port |
Port the app listens on inside the container, emitted as
|
docs_path |
OpenAPI/Swagger UI location, emitted as |
health_path |
Readiness-check endpoint, emitted as |
rate_limit |
Optional per-IP throttle, emitted as |
cors |
Optional logical; when not |
min_replicas |
Always-running instances, emitted as |
max_replicas |
Auto-scale ceiling, emitted as |
env |
Optional named list/vector of |
wrap |
If |
write |
If |
file |
Output path. Required when |
The YAML block as a single string, invisibly.
aurora_shinyproxy_yaml() for the interactive ShinyProxy spec.
cat(aurora_ruscker_yaml(image = "org/meu_app:latest", id = "meu_app"))cat(aurora_ruscker_yaml(image = "org/meu_app:latest", id = "meu_app"))
aurora_set_auth_cookie() writes an HttpOnly cookie carrying the token (at
login); aurora_clear_auth_cookie() removes it (at logout). In production
(HTTPS) pass secure = TRUE for Secure; SameSite=Strict; in development the
default uses SameSite=Lax so it works over plain HTTP on a different port.
aurora_set_auth_cookie(auth, response, token, secure = FALSE) aurora_clear_auth_cookie(auth, response, secure = FALSE)aurora_set_auth_cookie(auth, response, token, secure = FALSE) aurora_clear_auth_cookie(auth, response, secure = FALSE)
auth |
An |
response |
The reqres response object (the |
token |
The token string from |
secure |
Whether to set |
The response, invisibly.
Generates the YAML spec entry that goes under proxy.specs in a ShinyProxy
configuration, pointing at a built aurora image (see aurora_build_image()).
aurora apps are ordinary Docker-backed apps from ShinyProxy's point of view:
the spec just needs the image and the port the app listens on.
aurora_shinyproxy_yaml( image, dir = ".", id = NULL, display_name = NULL, description = NULL, port = 8000L, env = NULL, wrap = FALSE, write = FALSE, file = NULL )aurora_shinyproxy_yaml( image, dir = ".", id = NULL, display_name = NULL, description = NULL, port = 8000L, env = NULL, wrap = FALSE, write = FALSE, file = NULL )
image |
Container image tag, e.g. |
dir |
App directory, used only to default |
id |
Spec id. Defaults to the app name. |
display_name |
Human-facing name. Defaults to the app name. |
description |
Optional one-line description. |
port |
Port the app listens on inside the container (the aurora default
is |
env |
Optional named list/vector of |
wrap |
If |
write |
If |
file |
Output path. Required when |
The YAML block as a single string, invisibly.
cat(aurora_shinyproxy_yaml(image = "org/meu_app:latest", id = "meu_app"))cat(aurora_shinyproxy_yaml(image = "org/meu_app:latest", id = "meu_app"))
Wraps jsonlite::unbox() so a length-1 value serializes as a JSON scalar
instead of a 1-element array, while NULL/empty input returns NULL (which
serializes as JSON null or is dropped) instead of []. The [] case is a
common footgun: a frontend doing if (x) or x[0] misbehaves on an empty
array where it expected a scalar or null.
aurora_unbox(x)aurora_unbox(x)
x |
A length-1 value, or |
jsonlite::unbox(x) for a scalar, or NULL.
aurora_unbox("2026-06-02") aurora_unbox(NULL)aurora_unbox("2026-06-02") aurora_unbox(NULL)
Convenience for building dropdown/filter option lists from a column:
sorted, unique, with NA dropped. Returns an empty character vector for
NULL/empty input (serializes as [], the right shape for an empty list).
aurora_unique(x)aurora_unique(x)
x |
A vector, or |
The sorted unique non-NA values.
aurora_unique(c("b", "a", NA, "a"))aurora_unique(c("b", "a", NA, "a"))