How to Make an R REST API: A Beginners Guide to Plumber
<em><strong>Updated</strong>: November 1. 2022</em> <h2><span data-preserver-spaces="true">R REST API with Plumber</span></h2> <span data-preserver-spaces="true">REST APIs are everywhere around us. Software engineers use them to develop backend logic, and data scientists use them to deploy machine learning models. But what exactly is a REST API and should you as a data professional know how to code them from scratch? The answer is unquestionably yes, and you'll why (and how) shortly.</span> <span data-preserver-spaces="true">Today you'll learn how to make a basic R REST API with the <code>plumber</code> package. It's an R package that makes it easy to write R REST APIs and documentation around them. In this article, we'll make a REST API around the well-known Gapminder dataset.</span> <blockquote><span data-preserver-spaces="true">Completely new to R? </span><a class="editor-rtfLink" href="https://wordpress.appsilon.com/r-for-programmers/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">Check out our detailed R tutorial for programmers</span></a><span data-preserver-spaces="true">.</span></blockquote> <span data-preserver-spaces="true">Jump to a section:</span> <ul><li><a href="#introduction">Introduction to REST APIs</a></li><li><a href="#develop">Develop a Simple R REST API with Plumber</a><ul><li><a href="#endpoint-1">Endpoint 1 - /countries</a></li><li><a href="#endpoint-2">Endpoint 2 - /plot</a></li><li><a href="#endpoint-3">Endpoint 3 - /calculate_gdp</a></li></ul> </li> <li><a href="#conclusion">Summing up R REST API</a></li> </ul> <hr /> <h2 id="introduction"><span data-preserver-spaces="true">Introduction to REST APIs</span></h2> <span data-preserver-spaces="true">REST stands for "Representational State Transfer". In simpler words, it represents a set of rules that developers follow when creating APIs. The most common rule is that you should get a piece of data (response) whenever you make a request to a particular URL.</span> <span data-preserver-spaces="true">The request you make is made of four components:</span> <ul><li><strong><span data-preserver-spaces="true">Endpoint</span></strong><span data-preserver-spaces="true"> - a part of the URL you visit. For example, the endpoint of the URL </span><em><span data-preserver-spaces="true">https://example.com/predict</span></em><span data-preserver-spaces="true"> is </span><em><span data-preserver-spaces="true">/predict</span></em></li><li><strong><span data-preserver-spaces="true">Method</span></strong><span data-preserver-spaces="true"> - a type of request you're sending, can be either GET, POST, PUT, PATCH, and DELETE. They are used to perform one of these actions: Create, Read, Update, Delete (CRUD)</span></li><li><strong><span data-preserver-spaces="true">Headers</span></strong><span data-preserver-spaces="true"> - used for providing information (think authentication credentials, for example). They are provided as key-value pairs</span></li><li><strong><span data-preserver-spaces="true">Body</span></strong><span data-preserver-spaces="true"> - information that is sent to the server. Used only when not making GET requests.</span></li></ul> <span data-preserver-spaces="true">Most of the time, the response returned after making a request is in JSON format. The alternative format is XML, but JSON is more common. You can also return other objects, such as </span><strong><span data-preserver-spaces="true">images</span></strong><span data-preserver-spaces="true"> instead. You'll learn how to do that today.</span> <span data-preserver-spaces="true">R allows you to develop REST APIs with the <code>plumber</code> package. You can read the official documentation </span><a class="editor-rtfLink" href="https://www.rplumber.io/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">here</span></a><span data-preserver-spaces="true">.</span> <span data-preserver-spaces="true">It's easy to repurpose any R script file to an API with <code>plumber</code>, because you only have to decorate your functions with comments. You'll see all about it in a bit.</span> <h2 id="develop"><span data-preserver-spaces="true">Develop a Simple R REST API with Plumber</span></h2> <span data-preserver-spaces="true">To start, create an empty R script file. You'll need a couple of packages:</span> <ul><li><span data-preserver-spaces="true"><code>plumber</code> - to develop the API</span></li><li><span data-preserver-spaces="true"><code>dplyr</code> - to filter datasets based on the request body (or URL params)</span></li><li><span data-preserver-spaces="true"><code>ggplot2</code> - for data visualization</span></li><li><span data-preserver-spaces="true"><code>gapminder</code> - for data. That's what you'll use as a basis for the API.</span></li></ul> <span data-preserver-spaces="true">You can place two <code>roxigen2</code>-like comments for specifying API title and description. These two aren't mandatory, but you shouldn't skip them. Here's how the entire code snippet (imports, name, and description) looks like:</span> <pre><code class="language-r"> library(plumber) library(dplyr) library(ggplot2) library(gapminder) <br>#* @apiTitle Gapminder API #* @apiDescription API for exploring Gapminder dataset</code></pre> <span data-preserver-spaces="true">You're now ready to create your first endpoint.</span> <h3 id="endpoint-1"><span data-preserver-spaces="true">Endpoint 1 - /countries</span></h3> <span data-preserver-spaces="true">The idea behind this endpoint is that it should return countries and their respective data after a couple of filters are applied. To be more precise, this endpoint accepts parameter values for continent, life expectancy, and population. Value for continent must be exact, and values for the other two parameters filter data so that only rows with greater values are returned.</span> <span data-preserver-spaces="true">If you want to do things right, it'll require many comments, as you've seen previously. It's a good practice to write a short description, and list the parameters, and it's mandatory to specify the request type and the endpoint.</span> <span data-preserver-spaces="true">Below the comments, you'll place a function that performs the necessary logic and returns the results.</span> <span data-preserver-spaces="true">Let's think about the parameters for a second. You'll need:</span> <ul><li><strong><span data-preserver-spaces="true">continent</span></strong><span data-preserver-spaces="true"> - column </span><em><span data-preserver-spaces="true">continent</span></em></li><li><strong><span data-preserver-spaces="true">life expectancy</span></strong><span data-preserver-spaces="true"> - column </span><em><span data-preserver-spaces="true">lifeExp</span></em></li><li><strong><span data-preserver-spaces="true">population</span></strong><span data-preserver-spaces="true"> - column </span><em><span data-preserver-spaces="true">pop</span></em></li></ul> <span data-preserver-spaces="true">All three are mandatory, and you can do the filtering based on the parameter values with the <code>dplyr</code> package. This endpoint will return data for the most recent year only, which is 2007.</span> <span data-preserver-spaces="true">Here's the complete logic behind /countries endpoint:</span> <pre><code class="language-r"> #* Returns countries that satisfy condition #* @param in_continent #* @param in_lifeExpGT Life expectancy greater than #* @param in_popGT Population greater than #* @get /countries function(in_continent, in_lifeExpGT, in_popGT) { gapminder %>% filter( year == 2007, continent == in_continent, lifeExp > in_lifeExpGT, pop > in_popGT ) }</code></pre> <span data-preserver-spaces="true">If you were to run the API now, this is what you'd see:</span> <img class="size-full wp-image-6415" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b39595e1bdb8b84cc21493_1-2.webp" alt="Image 1 - API documentations page" width="2448" height="1146" /> Image 1 - API documentations page <span data-preserver-spaces="true">The endpoint on the bottom of the image (blue box) is clickable. Clicking on it expands a whole another section:</span> <img class="size-full wp-image-6416" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b39596e537a8d80f363616_2-2.webp" alt="Image 2 - Documentation for the /countries endpoint" width="2376" height="1110" /> Image 2 - Documentation for the /countries endpoint <span data-preserver-spaces="true">You can click on the "Try it out" button to make a request straight from the browser. You'll have to fill in the parameter values then; let's say like this:</span> <img class="size-full wp-image-6417" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b395bd63ab396182e98386_3-2.webp" alt="Image 3 - Testing out /countries endpoint" width="2380" height="1216" /> Image 3 - Testing out /countries endpoint <span data-preserver-spaces="true">Once you click on the "Execute" button, the response will show below. Here's what it looks like in this case:</span> <img class="size-full wp-image-6418" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b395963c7fcbe4a2ce763e_4-2.webp" alt="Image 4 - /countries endpoint example response" width="2106" height="946" /> Image 4 - /countries endpoint example response <span data-preserver-spaces="true">And that's your first endpoint! It takes data in and returns data out. But what if you want to return something else? Like an image, for example. You'll learn how to do that next.</span> <h3 id="endpoint-2"><span data-preserver-spaces="true">Endpoint 2 - /plot</span></h3> <span data-preserver-spaces="true">This endpoint will be quite different. The goal now is to return an image instead of raw data. The image will contain a line plot made with <code>ggplot2</code>, showing life expectancy over time.</span> <span data-preserver-spaces="true">Two parameters are required - country and chart title - both are self-explanatory.</span> <span data-preserver-spaces="true">If you want to return an image from an API with R, you'll have to put the following comment: <code>#* @serializer contentType list(type='image/png')</code>. Everything else is more or less the same.</span> <span data-preserver-spaces="true">Regarding the visualization, a subset is made from the original dataset containing only records for the specified country. A simple line and marker plot are then made from the dataset.</span> <span data-preserver-spaces="true">The problem is - you can't return a <code>ggplot2</code> visualization. You'll have to save the image with the <code>ggsave()</code> function and then return it with the <code>readBin()</code> function.</span> <span data-preserver-spaces="true">Here's the entire snippet:</span> <pre><code class="language-r"> #* Returns a line plot of life expectancy for country #* @param in_country #* @param in_title Chart title #* @get /plot #* @serializer contentType list(type='image/png') function(in_country, in_title) { subset <- gapminder %>% filter(country == in_country) plot <- ggplot(subset, aes(x = year, y = lifeExp)) + geom_line(color = "#0099f9", size = 2) + geom_point(color = "#0099f9", size = 5) + ggtitle(in_title) + theme_classic() + theme(aspect.ratio = 9 / 16) file <- "plot.png" ggsave(file, plot) readBin(file, "raw", n = file.info(file)$size) }</code></pre> <span data-preserver-spaces="true">If you were to run the API now, a new endpoint would immediately catch your attention:</span> <img class="size-full wp-image-6419" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b39597e537a8d80f363681_5-2.webp" alt="Image 5 - /plot endpoint" width="2244" height="100" /> Image 5 - /plot endpoint <span data-preserver-spaces="true">Here's how its documentation looks like:</span> <img class="size-full wp-image-6420" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b39599585f274a4fb9fde1_6-2.webp" alt="Image 6 - /plot endpoint documentation" width="2236" height="884" /> Image 6 - /plot endpoint documentation <span data-preserver-spaces="true">You can once again click on the "Try it out" button to test the functionality. </span><span data-preserver-spaces="true">Let's see how life expectancy changed over time in Poland:</span> <img class="size-full wp-image-6421" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d58f109e5d60f59d4a0b_5a29c3d6_7-2.webp" alt="Image 7 - Testing out the /plot endpoint for life expectancy in Poland" width="2236" height="932" /> Image 7 - Testing out the /plot endpoint for life expectancy in Poland <span data-preserver-spaces="true">Once you click on the "Execute" button, you'll be presented with the following visualization:</span> <img class="size-full wp-image-6422" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d58f2210fb399671563e_2ed44523_8-2.webp" alt="Image 8 - Life expectancy in Poland over time" width="1858" height="1554" /> Image 8 - Life expectancy in Poland over time <span data-preserver-spaces="true">And that's how you can return an image in the API response. You've only created endpoints with the GET method so far. You'll learn how to work with POST next.</span> <h3 id="endpoint-3"><span data-preserver-spaces="true">Endpoint 3 - /calculate_gdp</span></h3> <span data-preserver-spaces="true">You'll now learn how to work with POST methods (or any other sends data in the request body). The goal is to create another endpoint that calculates the total GDP for a specified country in the latest year (2007).</span> <span data-preserver-spaces="true">The only parameter you'll need is the country.</span> <span data-preserver-spaces="true">Once you have this value, you can use <code>dplyr</code> and the <code>summarize()</code> function to calculate the total GDP. Here's the entire code snippet:</span> <pre><code class="language-r">#* Returns most recent GDP for a country #* @param in_country #* @post /calculate_gdp function(in_country) { gapminder %>% filter( year == 2007, country == in_country ) %>% summarize(gdp = pop * gdpPercap) }</code></pre> <span data-preserver-spaces="true">If you were to run the API now, you would instantly see a new box, which is green this time, indicating the POST method:</span> <img class="size-full wp-image-6423" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b395c14015033f3dba4fd8_9-1.webp" alt="Image 9 - /calculate_gdp endpoint" width="2110" height="102" /> Image 9 - /calculate_gdp endpoint <span data-preserver-spaces="true">You can once again click on the "Try it out" button to test the functionality:</span> <img class="size-full wp-image-6424" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b395c287211fe5116f538d_10-1.webp" alt="Image 10 - Testing out the /calculate_gdp endpoint" width="2110" height="640" /> Image 10 - Testing out the /calculate_gdp endpoint <span data-preserver-spaces="true">Let's see what was the total GDP of Poland in 2007:</span> <img class="size-full wp-image-6425" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b395c275449ff9607134b4_11-1.webp" alt="Image 11 - Testing out the /calculate_gdp endpoint for Poland" width="2112" height="718" /> Image 11 - Testing out the /calculate_gdp endpoint for Poland <span data-preserver-spaces="true">Once you click on the "Execute" button, you'll be presented with the following response:</span> <img class="size-full wp-image-6426" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b395c351a4dda066143d9f_12-1.webp" alt="Image 12 - Total GDP in Poland in 2007" width="1848" height="256" /> Image 12 - Total GDP in Poland in 2007 <span data-preserver-spaces="true">The only difference here between GET and POST is that you can't put parameters and their values in the URL for the POST. The parameters and values are passed in the request body as JSON.</span> <span data-preserver-spaces="true">You now know how to wrap your R code into a simple REST API. Let's wrap things up next.</span> <hr /> <h2 id="conclusion"><span data-preserver-spaces="true">Summing up R REST API</span></h2> <span data-preserver-spaces="true">You've learned a lot today - what REST APIs are, what's the deal with the <code>plumber</code> package, and how to use it to build basic APIs in the R programming language. </span> <span data-preserver-spaces="true">APIs in R are commonly developed for exposing the predictive functionality of machine learning models. You can do other things, of course (as demonstrated today), but R probably isn't the best language for doing so. You're probably better of with Java, Go, or JavaScript if that's the case.</span> <h3><span data-preserver-spaces="true">Learn More</span></h3><ul><li><a class="editor-rtfLink" href="https://wordpress.appsilon.com/r-for-programmers/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">What Can I Do With R? 6 Essential R Packages for Programmers</span></a></li><li><a class="editor-rtfLink" href="https://wordpress.appsilon.com/r-dplyr-tutorial/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">How to Analyze Data with R: A Complete Beginner Guide to dplyr</span></a></li><li><a class="editor-rtfLink" href="https://wordpress.appsilon.com/r-linear-regression/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">Machine Learning with R: A Complete Guide to Linear Regression</span></a></li><li><a class="editor-rtfLink" href="https://wordpress.appsilon.com/r-logistic-regression/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">Machine Learning with R: A Complete Guide to Logistic Regression</span></a></li></ul>