drogonR::pr_run)If you already have a plumber service, the shim lets you run it under
drogonR by changing one line. The shim parses the plumber router into
drogonR routes and dispatches them through dr_serve().
Existing handlers, paths, and parameter types keep working.
For the overall picture see
vignette("drogonR", package = "drogonR").
Existing plumber code:
library(plumber)
pr <- pr() |>
pr_get ("/users/<id:int>", function(id) list(id = id, ok = TRUE)) |>
pr_post("/users", function(req) {
body <- jsonlite::fromJSON(req$postBody)
list(created = body$name)
})
plumber::pr_run(pr, port = 8080L, docs = FALSE) # <-- beforeBecomes:
That’s the whole change. docs, swagger,
swaggerCallback, quiet are silently accepted
and ignored (the shim has no swagger surface, so the flags are
inapplicable but valid). Other arguments — threads,
workers, max_queue — forward to
dr_serve().
@get, @post, @put,
@delete annotations and the
pr_get/post/put/delete() helpers.<name> and
<name:type>. Recognised types are int /
integer, dbl / double /
numeric, bool / logical; anything
else passes through as character. Coercion runs only on path parameters
— query and body values keep plumber’s untyped shape (string for query,
parsed-JSON for body).path > query > JSON body, with req
injected if the handler declares a req parameter.jsonlite::toJSON(auto_unbox = FALSE) for every return
value. Bare strings become JSON arrays (["hello"]), exactly
as plumber sends them — byte-level parity with
plumber::pr_run(). If you’ve already built a JSON string
with jsonlite::toJSON(), it’s emitted verbatim.dr_response() / dr_json() /
dr_text() from a handler opts out of the default serializer
and is forwarded as-is — useful for incrementally migrating hot
endpoints to drogonR’s response shape without leaving the shim.Each of these triggers an explicit error at pr_run()
time, before any route is registered, so failure is loud:
@filter / pr_filter() —
user-defined filters. Rewrite as middleware via dr_use()
(see vignette("mode-native")).pr_hook() / @hook —
preroute / postroute / postserialize hooks. Same migration path as
filters.pr_mount() / sub-routers — composing
one router from several. Flatten into a single pr() (or
move to native dr_app()).dr_response(headers = list("Content-Type" = "...")) if
you need another format.PlumberResponse / PlumberFile
return values — return a list / data.frame for JSON, a string
for text, or a dr_response() for full control.res parameter in handlers —
plumber-style mutation of a passed-in res object isn’t
supported. The shim warns once per affected route at
pr_run() time. Set status / headers via the return value
(dr_response(...)).library(plumber)
library(drogonR)
pr <- pr() |>
pr_get ("/health", function() list(ok = TRUE)) |>
pr_get ("/users/<id:int>", function(id) {
list(id = id, type = typeof(id)) # id arrives as integer
}) |>
pr_post("/echo", function(req) {
list(received = jsonlite::fromJSON(req$postBody))
})
drogonR::pr_run(pr, port = 8080L, docs = FALSE)Three responses your client will see:
GET /health -> {"ok":[true]}
GET /users/42 -> {"id":[42],"type":["integer"]}
POST /echo {"a":1} -> {"received":{"a":[1]}}
The bracketed scalars are plumber’s default serialiser
(auto_unbox = FALSE) — preserved on purpose so existing
clients don’t break. To get unboxed JSON, return
dr_json(x, auto_unbox = TRUE) from the handler; that
bypasses the default serializer.
The shim is fine for steady-state plumber apps. Reach for the native
API (vignette("mode-native")) if you want:
dr_response() calls,dr_text, dr_html,
dr_redirect, dr_file),req$params,
dr_query(), dr_body()),:id / <id> /
{id} syntax (the shim rewrites plumber’s
<id:type> form internally).If your hot endpoint is C/C++-bound (model inference, embeddings),
register that endpoint with dr_get_cpp() and leave the rest
under the shim — see
vignette("mode-cpp-shared", package = "drogonR").