Lecture 15
Plumber allows you to create a web API by merely decorating your existing R source code with roxygen2-like comments. Take a look at an example.
# plumber.R #* Echo back the input #* @param msg The message to echo #* @get /echo function(msg="") { list(msg = paste0("The message is: '", msg, "'")) } #* Plot a histogram #* @serializer png #* @get /plot function() { rand <- rnorm(100) hist(rand) }These comments allow plumber to make your R functions available as API endpoints.
Your plumber API is just an R script, so you can run it just like you would any other R script (but this won’t do much) since we need both the code and the metadata / decorators.
If you are using RStudio it will likely identify the script as a plumber API and provide a “Run API” button in the script editor.
Plumber makes use of comment based decorators to determine the structure and behavior of the API.
There are a number of other packages that use a similar approach, including roxygen2, Rcpp, and others that we will be seeing later in the class.
Some useful details:
Decorators preceed the function (endpoint) definition
Either #' or #* can be used but the latter is preferred
@cmd based annotations are used to specify the behavior of an endpoint
Endpoints are therefore just decorated anonymous R functions (the name/endpoint is determined by the decorator).
Every endpoint must have (at least) one of the following annotations and a path.
@get@post@put@delete@headthese determine the HTTP method that the endpoint will respond to.
Endpoints can have a single verb or multiple verbs, in the latter case your code will need to handle the different verbs. Similarly, the same endpoint may be created multiple times with different decorator verbs.
The two examples below are equivalent in the endpoints created, but there may be advantages with either approach depending on the use case.
A plumber function’s arguments determine the query parameters that the endpoint will accept.
Including the @param annotation is optional, but allows for more detailed documentation of the parameters (and shows up in the autogenerated Swagger page).
Plumber also supports dynamic routes, which are specified by including a parameter in the path rather than as part of a query string. This is a common feature in many REST APIs (e.g. GitHub’s).
One or more parameters are specified in the path by wraping the parameter name with <>.
By default plumber treats all parameters as strings (character), and your function is responsible for any type coercion necessary.
In the case of parameters from dynamic routing - the type can be specified in the path using : followed by bool, logical, double, numeric, or int.
By default, the return value of a plumber endpoint is serialized to JSON (via jsonlite). This can be overridden by using the @serializer annotation.
Some of the available serializers,
| Annotation | Content Type | Description/References |
|---|---|---|
@serializer html |
text/html; charset=UTF-8 |
Passes response through without |
@serializer json |
application/json |
Object processed with jsonlite::toJSON() |
@serializer csv |
text/csv |
Object processed with readr::format_csv() |
@serializer text |
text/plain |
Text output processed by as.character() |
@serializer print |
text/plain |
Text output captured from print() |
@serializer cat |
text/plain |
Text output captured from cat() |
@serializer jpeg |
image/jpeg |
Images created with jpeg() |
@serializer png |
image/png |
Images created with png() |
@serializer svg |
image/svg |
Images created with svg() |
@serializer pdf |
application/pdf |
PDF File created with pdf() |
req & resPlumber endpoint functions can also optionally have req and res arguments which capture the HTTP request and response objects respectively.
These objects are R environment objects and are useful for more advanced use cases, such as setting custom headers, cookies, or status codes.
There are three distinct ways that arguments / values can be passed to an endpoint function:
Query string parameters (e.g. ?a=1&b=2)
Dynamic path parameters (e.g. /user/<user_id>/)
Body parameters (e.g. POST or PUT requests)
Only the first two are directly accessible as arguments to the endpoint function. The third is accessible via the req object. It is possible for these to collide, and Plumber has a specific order of precedence for how these are resolved.
All three are accessible within the req environment using req$argsPath, req$argsBody, and req$argsQuery. There is also req$args which is a list of all three combined.
If any of your plumber code throws an error, the error will be caught and returned as a JSON object with the error message and a 500 HTTP status code.
More specific HTTP errors can be returned by modifying the res$status and res$body fields.
For some apps it is useful to have a shared state between endpoints. This can be achieved in a number of different ways - from the use of global variables, to file-based storage, to databases.
A simple example of the former,
Sta 323 - Spring 2025