blazers

Collections & records

Collections are the heart of Blazers — the equivalent of PocketBase collections and Supabase tables, but with a deliberately small surface.

Collections

A collection has a name and a JSON schema:

{
  "name": "posts",
  "schema": {
    "title": "string",
    "body?": "string",
    "views": "integer",
    "draft": "boolean",
    "meta": "object"
  },
  "created_at": "...",
  "updated_at": "..."
}

Schema format

schema is a flat JSON object: field name → type string.

Type SQLite column Validation
string TEXT must be a JSON string
email TEXT string (format not enforced)
url TEXT string
number REAL must be a JSON number
integer INTEGER must be an integer
boolean INTEGER must be true/false
array TEXT JSON-encoded array
object TEXT JSON-encoded object

Append ? to the field name to make it optional:

{ "slug?": "string", "tags?": "array" }

Fields not in the schema are still stored inside the record's data JSON blob — the schema governs typed columns and required-field validation, not a hard column allowlist. {} (or omitting schema) means schemaless.

Updating a schema

PATCH /api/collections/{name} with {"schema": "..."} runs migrate_collection_schema, which adds new columns and relaxes types. Changing a field's type on existing data is not a migration tool — treat schema edits as additive.

Records

A record is { id, data, created_at, updated_at }. data is arbitrary JSON; typed schema fields are validated on create/update (validate_data).

{
  "id": "rec_...",
  "data": { "title": "Hello", "views": 3, "draft": false },
  "created_at": "2025-01-01T00:00:00Z",
  "updated_at": "2025-01-01T00:00:00Z"
}

You may supply your own id on create (CreateRecordRequest.id) — useful for imports and idempotent seeds.

GET /api/collections/{name}/records accepts:

Param Default Notes
page 1 1-based
per_page 20 clamped to 1..=100
search "" substring match across record data

Response:

{
  "records": [...],
  "total": 137,
  "page": 1,
  "per_page": 20,
  "total_pages": 7
}

Permissions

Collection access is governed by RBAC permissions: each user has a role (viewer, editor, admin) per collection name, or * for global admin. Handlers call rbac::require_role(conn, user_id, name, "viewer") before touching records.