npm package: @fine-dev/vibe-backend
Description: **An open-source backend platform for building full-stack apps on Cloudflare.**
Latest version: 0.0.10

# Fine

**An open-source backend platform for building full-stack apps on Cloudflare.**

Fine provides database access, authentication, file storage, and row-level security—all deployable to the edge with Cloudflare Workers, D1, and R2. Originally built to power [Fine.dev](https://fine.dev), it's now available for anyone to self-host, extend, and contribute to.

---

## ✨ Features

* 🔐 **Authentication** – Session-based auth via email/password or magic links
* 🥮 **D1 Database API** – RESTful access with full CRUD support
* 📆 **R2 File Storage** – Entity-linked uploads, downloads, and permissions
* 🔐 **Row-Level Security (RLS)** – Policy-based access per user
* 🔐 **Secrets Management** – Secure config binding in Workers
* 🤖 **AI Assistant Integration** – Bring LLMs into your workflows (optional)

---

## 🚀 Quick Start

The simplest way to get started is by generating a project at [https://fine.dev](https://fine.dev).

### Setup and Deploy manually

1. Configure Cloudflare Workers
2. Set up D1 Database
3. Create R2 Bucket
4. Deploy the worker
5. Set up environment variables:

### Environment Variables

- `DB`: D1 Database binding
- `STORAGE_BUCKET`: R2 Bucket binding for file storage
- `BYPASS_AUTH`: Set to true to bypass authentication (development only)
- `VERSION`: API version information

6. Run:
```bash
npm install
npm run dev
```

Before first run:

```bash
npx wrangler d1 execute BAAS_DATABASE_NAME --local --file=./schema.sql
```

To deploy:

```bash
npm run deploy
```

> ⚠️ Fine is currently in **alpha**. CLI and self-hosting workflows are actively improving.

---

## 🌐 Stay Connected

* 🗨️ [Discord](https://discord.gg/nxW8sA5yqe)
* 🕊️ [X](https://x.com/FineDotDev)
* 🌎 [fine.dev](https://fine.dev)

> Built for builders. Powered by Cloudflare. Fully yours to self-host.

---

## 📂 Storage API

The Storage API allows clients to upload, download, and manage files, with access control tied to database entity permissions.

### Architecture

Files are stored in Cloudflare R2 and linked to database entities. Access permissions are inherited from the database's Row-Level Security (RLS) policies - if a user has access to an entity, they can access its associated files.

### Database Integration

All files are linked to database entities. To list files associated with any entity, you should query the database table directly. This ensures the database remains the single source of truth for file references.

### File Organization

Files are stored in R2 with the following path structure:
```
{table}/{id}/{field}/{filename}
```

For example, a user's profile picture might be stored at:
```
users/user123/avatar/profile.jpg
```

### API Endpoints

#### Upload a File
```
POST /storage/upload
Content-Type: multipart/form-data

Form Data:
- file: The file to upload
- entity: JSON object with {table, id, field}
- metadata: (optional) JSON object with custom metadata
```

#### Download a File
```
GET /storage/download/:table/:id/:field/:filename
```

#### Delete a File
```
DELETE /storage/delete/:table/:id/:field/:filename
```

### Example Usage: User Profile Picture

**Upload a profile picture:**
```javascript
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('entity', JSON.stringify({
  table: 'users',
  id: 'user123',
  field: 'avatar'
}));

fetch('/storage/upload', {
  method: 'POST',
  body: formData,
  headers: {
    'Authorization': 'Bearer your-token'
  }
});
```

**Display a profile picture:**
```html
<img src="/storage/download/users/user123/avatar/profile.jpg" alt="Profile Picture">
```

**Listing user profile pictures:**
```javascript
// Query the database to find file references
fetch('/db/tables/users?select=id,avatar')
  .then(response => response.json())
  .then(data => {
    // Process the file references from the database
    const users = data.data;
    users.forEach(user => {
      if (user.avatar) {
        // Display avatar using the download endpoint
        const avatarUrl = `/storage/download/users/${user.id}/avatar/${user.avatar}`;
        // Use the URL as needed
      }
    });
  });
```

### Access Control

File operations inherit RLS policies from the referenced entity:
- To upload/update a file: User needs UPDATE permission on the entity
- To download files: User needs SELECT permission on the entity
- To delete a file: User needs DELETE permission on the entity

# 🔐 D1 Database API with Row Level Security

Before first run, you need to migrate the database:

```
npx wrangler d1 execute BAAS_DATABASE_NAME --local --file=./schema.sql  
```

## Row Level Security (RLS)

This API implements Row Level Security, allowing you to define access policies at the row level. RLS restricts which rows can be retrieved by normal database operations based on user identity.

### How RLS Works

1. Each database operation (SELECT/INSERT/UPDATE/DELETE) is filtered through security policies.
2. Policies define conditions for when operations are allowed.
3. Two types of security checks:
   - `using_clause`: Filters which rows users can SELECT, UPDATE, or DELETE
   - `withcheck_clause`: Filters which rows users can INSERT or UPDATE

### Setting Up RLS Policies

Policies are stored in the `_policies` table. Here's how to create a policy:

```sql
-- Example: Allow users to see only their own todos
INSERT INTO _policies (table_name, action, using_clause) 
VALUES ('todos', 'select', 'user_id = $$CURRENT_USER$$');

-- Example: Allow users to insert only todos they own
INSERT INTO _policies (table_name, action, withcheck_clause) 
VALUES ('todos', 'insert', 'user_id = $$CURRENT_USER$$');

-- Example: Allow users to update their own todos
INSERT INTO _policies (table_name, action, using_clause, withcheck_clause) 
VALUES (
  'todos', 
  'update', 
  'user_id = $$CURRENT_USER$$', 
  'user_id = $$CURRENT_USER$$'
);

-- Example: Allow users to delete their own todos
INSERT INTO _policies (table_name, action, using_clause) 
VALUES ('todos', 'delete', 'user_id = $$CURRENT_USER$$');
```

Special variables in policy expressions:
- `$$CURRENT_USER$$`: The ID of the authenticated user
- `$$CURRENT_ROLE$$`: The role of the authenticated user (if available)

## Testing with cURL

Here are examples of how to use cURL to test the database endpoints:

### SELECT - Get all records

```bash
# Get all todos
curl -X GET "http://localhost:8787/db/tables/todos"

# Filter by field
curl -X GET "http://localhost:8787/db/tables/todos?completed=false"

# Advanced filtering, limit, and ordering
curl -X GET "http://localhost:8787/db/tables/todos?title.like=important&limit=10&order=created_at%20DESC"
```

### SELECT - Get a specific record

```bash
# Get todo by ID
curl -X GET "http://localhost:8787/db/tables/todos/123"
```

### INSERT - Create a new record

```bash
# Create a new todo
curl -X POST "http://localhost:8787/db/tables/todos" \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy groceries", "user_id": "user123", "completed": false}'
```

### UPDATE - Update records

```bash
# Update todo by ID
curl -X PATCH "http://localhost:8787/db/tables/todos/123" \
  -H "Content-Type: application/json" \
  -d '{"completed": true}'

# Update with a WHERE condition
curl -X PATCH "http://localhost:8787/db/tables/todos" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {"completed": true},
    "where": {"user_id": "user123"}
  }'
```

### DELETE - Delete records

```bash
# Delete todo by ID
curl -X DELETE "http://localhost:8787/db/tables/todos/123"

# Delete with a WHERE condition
curl -X DELETE "http://localhost:8787/db/tables/todos" \
  -H "Content-Type: application/json" \
  -d '{
    "where": {"completed": true}
  }'
```

### Advanced filtering examples

```bash
# Greater than
curl -X GET "http://localhost:8787/db/tables/todos?priority.gt=3"

# Less than or equal to
curl -X GET "http://localhost:8787/db/tables/todos?created_at.lte=2023-12-31"

# Not equal
curl -X GET "http://localhost:8787/db/tables/todos?status.neq=cancelled"

# LIKE pattern matching
curl -X GET "http://localhost:8787/db/tables/todos?title.like=meeting"
```