An open API service indexing awesome lists of open source software.

https://github.com/pachadotdev/openapi

An OpenAPI Specification (OAS) compliant REST API framework for R inspired by Python's Flask.
https://github.com/pachadotdev/openapi

Last synced: 15 days ago
JSON representation

An OpenAPI Specification (OAS) compliant REST API framework for R inspired by Python's Flask.

Awesome Lists containing this project

README

          

# openapi

[![Test coverage](https://raw.githubusercontent.com/pachadotdev/openapi/coverage/badges/coverage.svg)](https://github.com/USER/REPO/actions/workflows/test-coverage.yaml)
[![BuyMeACoffee](https://raw.githubusercontent.com/pachadotdev/buymeacoffee-badges/main/bmc-donate-white.svg)](https://www.buymeacoffee.com/pacha)

An OpenAPI Specification (OAS) compliant REST API framework for R inspired by Python's Flask.

This is designed to run as a service (e.g., via systemctl on Linux). Everything is returned as
JSON and this package allows to return data from SQL connections or any type of object that can
be serialized as JSON.

## Features

- OpenAPI 3.0 specification auto-generation
- Built-in Swagger UI at `/docs`
- OpenAPI spec served at `/openapi.json`
- Flask-like routing with piping support
- Automatic parameter extraction from query strings

## Installation

```r
remotes::install_github("pachadotdev/openapi")
```

## Simple example with piping

Paste this into an R console

```r
library(openapi)

api <- api_init(
title = "My API",
version = "1.0.0",
description = "A sample API built with openapi for R"
) |>
api_get("/", function() {
list(message = "Welcome to openapi for R!")
},
summary = "Root endpoint",
description = "Returns a welcome message"
) |>
api_get("/hello", function() {
list(greeting = "Hello World!")
},
summary = "Hello endpoint",
tags = c("greetings")
) |>
api_get("/greet", function(name) {
if (is.na(name)) name <- "Moto"
list(greeting = paste("Hello,", name, "!"))
},
summary = "Greet by name",
description = "Greets the user by name, defaults to 'Moto' if not provided",
tags = c("greetings")
)

# Run the server
api_run(api, host = "127.0.0.1", port = 5000, debug = TRUE)
```

then visit

- http://127.0.0.1:5000/ - Your API
- http://127.0.0.1:5000/docs - Swagger UI documentation
- http://127.0.0.1:5000/openapi.json - OpenAPI specification
- http://127.0.0.1:5000/hello
- http://127.0.0.1:5000/greet?name=World

## Example without piping

Undefined parameters are `NA`.

```r
library(openapi)

api <- api_init()

api <- api_get(api, "/mtcars1", function(cyl, am) {
# sanitize parameters
cyl <- as.integer(substr(cyl, 1, 1))
am <- as.integer(substr(am, 1, 1))

d <- mtcars

if (!is.na(cyl) && cyl > 0) { d <- d[d$cyl == cyl, ] }
if (!is.na(am) && am %in% 0:1) { d <- d[d$am == am, ] }

d
})

api <- api_get(api, "/mtcars2", function(cyl, vs) {
# sanitize parameters
cyl <- as.integer(substr(cyl, 1, 1))
vs <- as.integer(substr(vs, 1, 1))

d <- mtcars

if (!is.na(cyl) && cyl > 0) { d <- d[d$cyl == cyl, ] }
if (!is.na(vs) && vs %in% 0:1) { d <- d[d$vs == vs, ] }

d
})

api_run(api, host = "127.0.0.1", port = 5000, debug = TRUE)
```

then visit

http://127.0.0.1:5000/mtcars1?cyl=6
http://127.0.0.1:5000/mtcars1?cyl=4&am=0
etc.

## OpenAPI Specification

The package automatically generates an OpenAPI 3.0 specification from your routes.

### Accessing the spec

When the server is running:
- Swagger UI: http://127.0.0.1:5000/docs
- OpenAPI JSON: http://127.0.0.1:5000/openapi.json

### Exporting the spec

```r
# Get spec as an R list
spec <- api_spec(api, host = "127.0.0.1", port = 5000)

# Write spec to file
api_spec_write(api, path = "openapi.json", host = "127.0.0.1", port = 5000)
```

### Adding metadata to routes

```r
api <- api_init(
title = "My API",
version = "2.0.0",
description = "API description here"
) |>
api_get(
"/users",
function() { list(users = c("alice", "bob")) },
summary = "List all users",
description = "Returns a list of all registered users",
tags = c("users")
) |>
api_post(
"/users",
function(name) { list(created = name) },
summary = "Create a user",
tags = c("users")
)
```

## Running the server

### Blocking (for production)

Runs until Ctrl+C or that you interrupt the API service (see the systemctl example below)

```r
api_run(api, host = "0.0.0.0", port = 5000)
```

### Non-blocking (for development/testing)

```r
# returns modified api, so you must reassign
api <- api_run_background(api, port = 5000)

# do other things
httpuv::service() # process requests while doing other work

# stop when done
api_stop(api)
```

## Response formats

By default, return values are JSON-serialized (lists and data frames work automatically):

```r
# Error response with custom status
api <- api |>
api_get("/fail", function() {
response_error("Something went wrong", status = 400)
})
```

## Migrating from plumber v1

### Before (plumber)

```r
#* @get /hello
function() {
list(message = "Hello!")
}

#* @param name The name to greet
#* @get /greet
function(name = "World") {
list(greeting = paste("Hello,", name))
}
```

### After (openapi)

```r
api <- api_init() |>
api_get("/hello", function() {
list(message = "Hello!")
}) |>
api_get("/greet", function(name) {
if (is.na(name)) name <- "World"
list(greeting = paste("Hello,", name))
})

api_run(api)
```

## systemd service

Create `/etc/systemd/system/openapi-api.service` or similar:

```ini
[Unit]
Description=OpenAPI R API
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/path/to/api
ExecStart=/usr/bin/Rscript api.R
Restart=always

[Install]
WantedBy=multi-user.target
```

Then:

```bash
sudo systemctl daemon-reload
sudo systemctl enable openapi-api
sudo systemctl start openapi-api
```

To restart/stop the API:

```bash
sudo systemctl restart openapi-api
sudo systemctl stop openapi-api
```

## License

Apache Licence 2.0