Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nickruigrok/baseflare/llms.txt

Use this file to discover all available pages before exploring further.

By the end of this guide you will have a working Cloudflare Worker powered by Baseflare. You will define a todos collection with a schema, write a listTodos query and a createTodo mutation, wire them into the Worker entry point, and call both endpoints over HTTP. No Cloudflare account is required to follow along locally — you can run the Worker with Wrangler’s local mode or deploy it manually when you are ready.
1
Install Baseflare
2
Add the baseflare package to your project. It ships as a single package with focused subpath exports for baseflare/values and baseflare/server.
3
pnpm
pnpm add baseflare
npm
npm install baseflare
yarn
yarn add baseflare
4
Define your schema
5
Create a schema.ts file in your functions directory. Import defineSchema and defineTable from baseflare/server and the v validator namespace from baseflare/values. Each table definition describes the shape of its documents; .index() creates a SQLite index on the named fields so queries on those fields are fast.
6
import { defineSchema, defineTable } from "baseflare/server";
import { v } from "baseflare/values";

export const schema = defineSchema({
  todos: defineTable({
    ownerId: v.string(),
    text: v.string().min(1).max(280),
    completed: v.boolean().default(false),
    tags: v.array(v.string()).default([]),
  }).index("by_owner", ["ownerId"]),
});
7
Baseflare uses a document model: every table stores a single _data JSON column alongside its _id. The field definitions above are enforced at write time by the Worker runtime — no database-level constraints are required.
8
Define permissions
9
Create a rules.ts file and export your access rules with defineRules. Rules are deny-by-default: any table or operation that has no matching rule will refuse access. The ctx.auth.getUserIdentity() call resolves the caller’s identity from the request token.
10
import { defineRules } from "baseflare/server";

export const rules = defineRules({
  todos: {
    read: async ({ ctx, doc }) =>
      doc.ownerId === (await ctx.auth.getUserIdentity()),
    insert: async ({ ctx, value }) =>
      value.ownerId === (await ctx.auth.getUserIdentity()),
    update: async ({ ctx, existingDoc }) =>
      existingDoc.ownerId === (await ctx.auth.getUserIdentity()),
    delete: async ({ ctx, existingDoc }) =>
      existingDoc.ownerId === (await ctx.auth.getUserIdentity()),
  },
});
11
Each rule is an async function that receives typed context and returns a boolean. Returning false — or having no rule at all — denies the operation and surfaces a PERMISSION_DENIED error to the caller.
12
Write a query
13
Queries read documents and return typed data. They never write to the database. Create a queries.ts file and export a listTodos function using the query wrapper from baseflare/server.
14
import { query } from "baseflare/server";
import { v } from "baseflare/values";

export const listTodos = query({
  args: {
    ownerId: v.string(),
  },
  returns: v.array(v.any()),
  async handler(ctx, args) {
    return await ctx.db
      .query("todos")
      .filter({ ownerId: args.ownerId })
      .order("_createdAt", "desc")
      .limit(50)
      .collect();
  },
});
15
The ctx.db.query() call returns a fluent query builder. .filter() accepts a plain object of field matchers, .order() sorts by any field (including the derived _createdAt), .limit() caps the result set, and .collect() executes and returns all matching documents.
16
Write a mutation
17
Mutations are the atomic write primitive. Create a mutations.ts file and export a createTodo function using the mutation wrapper. The runtime validates the return value against the returns validator before committing any writes.
18
import { mutation } from "baseflare/server";
import { v } from "baseflare/values";

export const createTodo = mutation({
  args: {
    ownerId: v.string(),
    text: v.string().min(1).max(280),
  },
  returns: v.string(),
  async handler(ctx, args) {
    return await ctx.db.insert("todos", {
      ownerId: args.ownerId,
      text: args.text,
      completed: false,
      tags: [],
    });
  },
});
19
ctx.db.insert() validates the document against the schema, serializes it to JSON, writes it to D1, and returns the new document’s _id string — a plain UUIDv7.
20
Create the Worker
21
Wire your schema, rules, and functions into a Cloudflare Worker using createWorker from baseflare/server. The manifest requires a schema and typed function entry arrays. Each entry provides the function definition, an exportName (the name used in the source file), a modulePath (the module identifier), and a name that combines them as modulePath:exportName. That combined name becomes the RPC route segment.
22
import { createWorker } from "baseflare/server";

import { schema } from "./schema";
import { rules } from "./rules";
import { listTodos } from "./queries";
import { createTodo } from "./mutations";

export default createWorker({
  schema,
  rules,
  queryEntries: [
    {
      definition: listTodos,
      exportName: "listTodos",
      modulePath: "queries",
      name: "queries:listTodos",
    },
  ],
  mutationEntries: [
    {
      definition: createTodo,
      exportName: "createTodo",
      modulePath: "mutations",
      name: "mutations:createTodo",
    },
  ],
});
23
createWorker builds an internal function index and exposes RPC routes at /api/query/:name, /api/mutation/:name, and /api/action/:name, where :name is the modulePath:exportName identifier you provided. The Worker entry point also needs a D1 database binding named APP_DB — configure this in your wrangler.toml or via the Cloudflare dashboard.
24
Call the API
25
Once your Worker is running — locally via Wrangler or deployed to Cloudflare — you can call your functions over HTTP using a simple JSON body.
26
Call listTodos:
27
curl -X POST http://localhost:8787/api/query/queries:listTodos \
  -H "Content-Type: application/json" \
  -d '{"args": {"ownerId": "user_123"}}'
28
{
  "result": [
    {
      "_id": "019078e5-d29f-7b00-8000-1a2b3c4d5e6f",
      "_createdAt": 1709000000000,
      "ownerId": "user_123",
      "text": "Ship the Baseflare quickstart",
      "completed": false,
      "tags": []
    }
  ]
}
29
Call createTodo:
30
curl -X POST http://localhost:8787/api/mutation/mutations:createTodo \
  -H "Content-Type: application/json" \
  -d '{"args": {"ownerId": "user_123", "text": "Ship the Baseflare quickstart"}}'
31
{
  "result": "019078e5-d29f-7b00-8000-1a2b3c4d5e6f"
}
32
Every RPC request body must be a JSON object with exactly one key — "args" — whose value is the arguments object for your function. Any other shape returns a VALIDATION_ERROR.
The CLI deploy workflow (npx baseflare deploy) is in active development and is not yet available. For now, you can run the Worker locally with wrangler dev or deploy it manually using wrangler deploy or the Cloudflare dashboard. Point Wrangler at your worker.ts entry file and bind a D1 database named APP_DB.
Ready to go deeper? Check out the guides for schema definition, queries, mutations, and permissions.

Build docs developers (and LLMs) love