Skip to content

React + Plumber CORS problem #993

@Andryas

Description

@Andryas

System details

Output of sessioninfo::session_info()():

R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.2 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: America/Toronto
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] RSQLite_2.3.9      DBI_1.2.3          jsonlite_1.9.1     plumber_1.3.0.9000

loaded via a namespace (and not attached):
 [1] later_1.4.1       R6_2.6.1          fastmap_1.2.0     httpuv_1.6.15     bit_4.5.0         swagger_5.17.14.1
 [7] magrittr_2.0.3    webutils_1.2.2    cachem_1.1.0      blob_1.2.4        pkgconfig_2.0.3   memoise_2.0.1    
[13] bit64_4.5.2       lifecycle_1.0.4   promises_1.3.2    cli_3.6.4         vctrs_0.6.5       compiler_4.4.1   
[19] tools_4.4.1       Rcpp_1.0.14       rlang_1.1.5       crayon_1.5.3      stringi_1.8.4  

Example application or steps to reproduce the problem

library(plumber)
library(jsonlite)
library(DBI)
library(RSQLite)

# Initialize database connection
conn <- dbConnect(RSQLite::SQLite(), "items.db")

# Create table if not exists
dbExecute(
    conn,
    "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)"
)

# Define CORS headers
cors_handler <- function(req, res) {
    res$setHeader("Access-Control-Allow-Origin", "*")
    res$setHeader(
        "Access-Control-Allow-Methods",
        "GET, POST, PUT, DELETE, OPTIONS"
    )
    res$setHeader("Access-Control-Allow-Headers", "Content-Type")
    if (req$REQUEST_METHOD == "OPTIONS") {
        res$status <- 200
        return("")
    }
    forward()
}

# Define API
pr() %>%
    pr_hook("preroute", cors_handler) %>% # Apply CORS middleware
    # GET all items
    pr_get("/items", function(req, res) {
        items <- dbGetQuery(conn, "SELECT * FROM items")
        res$body <- toJSON(items, auto_unbox = TRUE)
        res
    }) %>%

    # POST a new item
    pr_post("/items", function(req, res) {
        body <- fromJSON(req$postBody)
        dbExecute(conn, "INSERT INTO items (name) VALUES (?)", list(body$name))
        id <- dbGetQuery(conn, "SELECT last_insert_rowid() AS id")$id
        res$body <- toJSON(list(id = id, name = body$name), auto_unbox = TRUE)
        res
    }) %>%

    # PUT (update) an item
    pr_put("/items/<id>", function(req, res, id) {
        body <- fromJSON(req$postBody)
        dbExecute(
            conn,
            "UPDATE items SET name = ? WHERE id = ?",
            list(body$name, id)
        )
        res$body <- toJSON(list(id = id, name = body$name), auto_unbox = TRUE)
        res
    }) %>%

    # DELETE an item
    pr_delete("/items/<id>", function(req, res, id) {
        dbExecute(conn, "DELETE FROM items WHERE id = ?", list(id))
        res$body <- toJSON(list(message = "Item deleted"), auto_unbox = TRUE)
        res
    }) %>%

    pr_run(port = 8000)
import React, { useEffect, useState } from "react";
import axios from "axios";

const API_URL = "http://127.0.0.1:8000/items";

function App() {
  const [items, setItems] = useState([]);
  const [newItem, setNewItem] = useState("");

  useEffect(() => {
    fetchItems();
  }, []);

  const fetchItems = async () => {
    const response = await axios.get(API_URL);
    setItems(response.data);
  };

  const addItem = async () => {
    if (!newItem.trim()) return;
    const response = await axios.post(API_URL, { name: newItem });
    setItems([...items, response.data]);
    setNewItem("");
  };

  const updateItem = async (id, newName) => {
    await axios.put(`${API_URL}/${id}`, { name: newName });
    setItems(items.map(item => (item.id === id ? { id, name: newName } : item)));
  };

  const deleteItem = async (id) => {
    await axios.delete(`${API_URL}/${id}`);
    setItems(items.filter(item => item.id !== id));
  };

  return (
    <div className="container">
      <h1>CRUD with React & Plumber</h1>
      <input
        type="text"
        value={newItem}
        onChange={(e) => setNewItem(e.target.value)}
        placeholder="Add new item"
      />
      <button onClick={addItem}>Add</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <input
              type="text"
              value={item.name}
              onChange={(e) => updateItem(item.id, e.target.value)}
            />
            <button onClick={() => deleteItem(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Describe the problem in detail

The problem is that while I can retrieve data from the API, any other method I try results in CORS errors. I even found a similar issue reported on Stack Overflow.

Image

https://stackoverflow.com/questions/78165697/react-post-plumber-api-resulting-in-cors-error

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions