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

https://github.com/nvelden/shinylive-d1-datatable

Editable Shiny DataTable in the browser with Shinylive, persisted through a Cloudflare Worker and D1 database.
https://github.com/nvelden/shinylive-d1-datatable

cloudflare-d1 cloudflare-workers datatable github-pages r shiny shinylive webr

Last synced: 8 days ago
JSON representation

Editable Shiny DataTable in the browser with Shinylive, persisted through a Cloudflare Worker and D1 database.

Awesome Lists containing this project

README

          

# Shinylive D1 DataTable

**Live demo:**

A small example of an editable Shiny DataTable that runs entirely in the
browser with [Shinylive](https://posit-dev.github.io/r-shinylive/), is hosted
on GitHub Pages, and persists rows through a [Cloudflare Worker](https://developers.cloudflare.com/workers/)
backed by [Cloudflare D1](https://developers.cloudflare.com/d1/).

This is a 2026 update of a [2019 tutorial](https://www.nielsvandervelden.com/blog/editable-datatables-in-r-shiny-using-sql/)
([repository](https://github.com/nvelden/sql_table)) that built the same table
on a Shiny server with a local SQLite file.

The Worker is the only thing that talks to D1. The browser sends a shared
secret in an `X-API-Key` header. That secret is included in the static bundle,
so it is a soft barrier against opportunistic abuse, not real authentication.
Use this pattern for tutorials and prototypes. For production data, replace
the shared secret with per-user authentication and add rate limiting.

## Project layout

```text
app.R # Shinylive/Shiny DataTable app
worker/src/index.js # Worker API for row CRUD
worker/wrangler.toml.example # Template for the Worker config (gitignored real one is copied from this)
worker/.dev.vars.example # Template for local secret file
migrations/0001_create_responses.sql # Table schema and seed rows
docs/ # Generated by `npm run export`; GitHub Pages serves this
scripts/export-shinylive.R # Clean Shinylive export helper
scripts/setup-local.sh # Local setup helper
```

## Prerequisites

- R with `shiny`, `DT`, `shinyjs`, and `shinylive`
- Node.js 22 or newer
- A Cloudflare account
- A GitHub account

Install the R packages if needed:

```r
install.packages(c("shiny", "DT", "shinyjs", "shinylive"))
```

Install [Wrangler](https://developers.cloudflare.com/workers/wrangler/),
Cloudflare's CLI:

```bash
npm install
```

Log in to Cloudflare:

```bash
npx wrangler login
```

## Run locally

The local helper creates `worker/.dev.vars`, seeds the local D1, and exports
the Shinylive bundle to `docs/`:

```bash
npm run local:setup
```

Then start the Worker and the static server in two terminals:

```bash
npm run worker:dev # Worker on http://localhost:8787
npm run site:dev # docs/ on http://localhost:8000
```

Open `http://localhost:8000`.

## Deploy to GitHub Pages and Cloudflare

Create a D1 database (skip if you already have one):

```bash
npm run d1:create
```

Wrangler prints a `database_id`. The real `worker/wrangler.toml` is gitignored
so the id never reaches the repo. If you ran `npm run local:setup` it was
already created from `worker/wrangler.toml.example`. Otherwise copy it
manually:

```bash
cp worker/wrangler.toml.example worker/wrangler.toml
```

Open `worker/wrangler.toml` and paste the id in. Set `ALLOWED_ORIGIN` to your
GitHub Pages origin (no trailing slash, no path):

```toml
[[d1_databases]]
binding = "SQL_TABLE_DB"
database_name = "your-d1-database-name"
database_id = "your-d1-database-id"

[vars]
ALLOWED_ORIGIN = "https://.github.io"
```

Seed the remote D1 with the schema:

```bash
npm run d1:migrate:remote
```

Generate a shared secret:

```bash
openssl rand -hex 32
```

Store it on the Worker (paste the same value when prompted):

```bash
npm run worker:secret
```

Deploy the Worker:

```bash
npm run worker:deploy
```

Wrangler prints the deployed URL. Open `app.R` and replace the two
placeholders in the JavaScript helper:

- `WORKER_URL` with the deployed URL above
- `SHARED_SECRET` with the value from `openssl rand`

Re-export the bundle so the new values land in `docs/`:

```bash
npm run export
```

Commit `docs/` along with your source changes and push:

```bash
git add docs worker app.R package.json
git commit -m "Initial deploy"
git push
```

In the GitHub repository, open **Settings → Pages**. Under
**Build and deployment**, set the source to the `main` branch and `/docs`
folder. After about a minute the site is live at:

```text
https://.github.io//
```

## Notes

- `docs/` is committed because GitHub Pages serves from a folder in the repo.
Re-export and commit again whenever `app.R` changes.
- `worker/.dev.vars` is gitignored. Never commit it.
- `SHARED_SECRET` ends up in the public JavaScript bundle on GitHub Pages.
Treat it as a tutorial guard, not real authentication.
- To deploy on Cloudflare Pages instead of, or alongside, GitHub Pages, the
repository also includes an `npm run cloudflare:deploy` script that uploads
`docs/` directly to a Cloudflare Pages project.