bunpaas-db
A schema-agnostic, multi-tenant backend for prototyping apps. Built on Bun and PostgreSQL with JSONB storage.
Try It
| Tenant ID | |
| Read-Only Token | |
| Read-Write Token | |
Quick Start
// Create a tenant
const res = await fetch("https://your-app.example.com/_tokens", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "My App" })
});
const { tenant, tokens } = await res.json();
// tokens.readOnly - can only read
// tokens.readWrite - can read and write
API
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /_tokens | None | Create tenant |
| GET | /_tokens | Write | Get tokens |
| POST | /_users | Write | Create user |
| PATCH | /_users/:id | Write | Change password |
| DELETE | /_users/:id | Write | Delete user |
| POST | /_signin | Read | Authenticate user |
| GET | /:collection | Read | List items |
| GET | /:collection/:id | Read | Get item |
| POST | /:collection | Write | Create item |
| PUT | /:collection/:id | Write | Replace item |
| PATCH | /:collection/:id | Write | Merge update |
| DELETE | /:collection/:id | Write | Delete item |
Authentication
All requests (except tenant creation) require a Bearer token:
Authorization: Bearer <token>
Creating a tenant returns a readOnly token (can only GET) and a readWrite token (full access).
Data Model
Items are stored with flexible JSONB data:
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated |
collection | TEXT | e.g., "tasks", "users" |
parent_id | UUID | Optional parent reference |
owner_id | UUID | Optional owner reference |
order_key | TEXT | Optional sort key |
data | JSONB | Your custom data |
created_at | TIMESTAMPTZ | Auto-set |
updated_at | TIMESTAMPTZ | Auto-updated |
CRUD
Create
const res = await fetch("https://your-app.example.com/tasks", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
data: { title: "My Task", status: "pending" },
parentId: "optional-uuid",
orderKey: "a"
})
});
const { data: task } = await res.json();
Read
// List items
const listRes = await fetch("https://your-app.example.com/tasks", {
headers: { "Authorization": `Bearer ${TOKEN}` }
});
const { data: tasks, pagination } = await listRes.json();
// Get single item
const getRes = await fetch(`https://your-app.example.com/tasks/${id}`, {
headers: { "Authorization": `Bearer ${TOKEN}` }
});
const { data: task } = await getRes.json();
Update
PUT replaces the entire item. PATCH merges with existing data.
// PUT - replaces data entirely
await fetch(`https://your-app.example.com/tasks/${id}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ data: { title: "New Title" } })
});
// PATCH - merges with existing data
await fetch(`https://your-app.example.com/tasks/${id}`, {
method: "PATCH",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ data: { status: "done" } })
});
Delete
await fetch(`https://your-app.example.com/tasks/${id}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${TOKEN}` }
});
Query Parameters
| Parameter | Example | Description |
|---|---|---|
limit | ?limit=10 | Max items (default: 25) |
offset | ?offset=20 | Skip items |
orderBy | ?orderBy=updated_at | Sort field |
parentId | ?parentId=uuid | Filter by parent |
ownerId | ?ownerId=uuid | Filter by owner |
filter | ?filter={"status":"done"} | JSON field filter |
expand | ?expand=parent,owner | Include related items |
Expand Relationships
Inline parent, owner, or children in a single request:
// Expand parent and owner
const res = await fetch(
`https://your-app.example.com/tasks/${id}?expand=parent,owner`,
{ headers: { "Authorization": `Bearer ${TOKEN}` } }
);
const { data: task } = await res.json();
// task._expanded.parent, task._expanded.owner
// Expand children from a collection
const boardRes = await fetch(
`https://your-app.example.com/boards/${boardId}?expand=children:cards`,
{ headers: { "Authorization": `Bearer ${TOKEN}` } }
);
// Nested expand: board -> columns -> cards -> owner
const deepRes = await fetch(
`https://your-app.example.com/boards/${boardId}?expand=children:columns.children:cards.owner`,
{ headers: { "Authorization": `Bearer ${TOKEN}` } }
);
User Management
// Create user
const userRes = await fetch("https://your-app.example.com/_users", {
method: "POST",
headers: {
"Authorization": `Bearer ${WRITE_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ email: "user@example.com", password: "secret" })
});
const { data: user } = await userRes.json();
// Sign in
const signinRes = await fetch("https://your-app.example.com/_signin", {
method: "POST",
headers: {
"Authorization": `Bearer ${READ_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ email: "user@example.com", password: "secret" })
});
// Change password
await fetch(`https://your-app.example.com/_users/${userId}`, {
method: "PATCH",
headers: {
"Authorization": `Bearer ${WRITE_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ password: "newpassword" })
});
// Delete user
await fetch(`https://your-app.example.com/_users/${userId}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${WRITE_TOKEN}` }
});
JavaScript Client
import { bunpaasDb } from "/assets/js/client.js";
// Create tenant
const { tokens } = await bunpaasDb("https://your-app.example.com").createTenant("My App");
// Use the API
const db = bunpaasDb("https://your-app.example.com", tokens.readWrite);
// CRUD
const { data: task } = await db.collection("tasks").create({ data: { title: "Test" } });
const { data: tasks } = await db.collection("tasks").list({ filter: { status: "done" } });
await db.collection("tasks").update(task.id, { data: { status: "done" } });
await db.collection("tasks").delete(task.id);