Better Auth Resource Manager
BARM creates authenticated CRUD endpoints and a typed client from a single Zod-based resource definition.
Requirements
- Node.js 20 or newer
- Better Auth 1.x
- A Better Auth database adapter
- Zod 4
Install
pnpm add @p8labs/barm better-auth zod
The package has three public entry points:
import { resourceManager } from "@p8labs/barm";
import { resourceManagerClient } from "@p8labs/barm/client";
import { resource, schema } from "@p8labs/barm/resource";
Define a resource
Resource fields use the BARM schema builder. The builder returns Zod schemas with database metadata attached.
import { resource, schema } from "@p8labs/barm/resource";
const todoSchema = schema
.object({
id: schema
.string()
.primaryKey()
.autofill(() => crypto.randomUUID()),
title: schema.string().index(),
completed: schema.boolean(),
userId: schema.string().references("user.id").owner(),
createdAt: schema.date().autofill(() => new Date()),
updatedAt: schema.date().autofill(() => new Date(), "createOrUpdate"),
})
.table("todo");
export const todo = resource({
schema: todoSchema,
});
Field metadata
| Method | Purpose |
|---|---|
primaryKey() |
Marks a unique primary key field. |
unique() |
Adds a unique database constraint. |
index() |
Marks a field for indexing. |
references("resource.field") |
Adds a foreign-key reference. |
owner() |
Fills the field with the authenticated user’s id on create. |
autofill(generate) |
Generates a server-managed value on create. |
autofill(generate, "update") |
Generates a value on update. |
autofill(generate, "createOrUpdate") |
Generates a value on create and update. |
table("name") |
Uses a different database model name. |
Supported primitive fields are string, number, boolean, and date.
Server-managed fields are excluded from typed client create and update inputs. The server always overwrites owner fields and autofilled fields, so clients cannot choose those values.
Configure Better Auth
Pass the resources to the server plugin alongside your database adapter:
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { resourceManager } from "@p8labs/barm";
import { todo } from "./resources.js";
import { prisma } from "./prisma.js";
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "sqlite",
}),
plugins: [
resourceManager({
resources: { todo },
}),
],
});
The database adapter must support the Better Auth adapter operations used by BARM: create, findOne, findMany, update, and delete.
Every generated endpoint uses Better Auth’s sessionMiddleware. Requests therefore need a valid Better Auth session.
Generated HTTP API
For a resource named todo, BARM registers:
| Operation | Method | Path | Success |
|---|---|---|---|
| List | GET |
/resource/todo |
200 |
| Get | GET |
/resource/todo/:id |
200 |
| Create | POST |
/resource/todo |
201 |
| Update | PATCH |
/resource/todo/:id |
200 |
| Delete | DELETE |
/resource/todo/:id |
200 |
List pagination
Use page and limit query parameters:
GET /resource/todo?page=2&limit=20
page starts at 1. limit is clamped between 1 and 100, with a default of 20. Invalid values fall back to the defaults.
The response shape is:
{
"data": [{ "id": "todo-1", "title": "Write docs", "completed": false }],
"pagination": {
"page": 1,
"limit": 20,
"hasMore": false
}
}
The server reads limit + 1 records to calculate hasMore; the extra record is not returned.
Create
POST /resource/todo
Content-Type: application/json
{ "title": "Write docs", "completed": false }
The response status is 201. Invalid data returns 400 with the Zod issues in the response data. Missing authentication for an owner() field throws an authentication error.
Get, update, and delete
GET /resource/todo/todo-1
PATCH /resource/todo/todo-1
DELETE /resource/todo/todo-1
Update accepts a partial resource input:
{ "completed": true }
Missing ids return 400. A missing record on get returns 404.
Typed client
Register the client plugin with the same resource definitions used by the server:
import { createAuthClient } from "better-auth/client";
import { resourceManagerClient } from "@p8labs/barm/client";
import { todo } from "./resources.js";
export const authClient = createAuthClient({
plugins: [
resourceManagerClient({
resources: { todo },
}),
],
});
Use the generated actions:
await authClient.resourceManager.todo.list({ page: 1, limit: 20 });
await authClient.resourceManager.todo.get({
id: "todo-1",
});
await authClient.resourceManager.todo.create({
title: "Write docs",
completed: false,
});
await authClient.resourceManager.todo.update({
id: "todo-1",
data: { completed: true },
});
await authClient.resourceManager.todo.delete({
id: "todo-1",
});
The client uses GET, POST, PATCH, and DELETE to match the generated server endpoints. Its response follows Better Auth’s fetch response shape: successful calls contain data and error: null; failed calls contain data: null and an error object.
Restricting endpoints
Set an operation to false in the resource access configuration to avoid registering that endpoint:
const readOnlyTodo = resource({
schema: todoSchema,
access: {
create: false,
update: false,
delete: false,
},
});
Access defaults to enabled for all five operations. Boolean false disables endpoint registration. Function rules run when a request arrives and can return a boolean or a promise. List and create rules receive { session } plus create data; get, update, and delete rules receive the current record, the session, and update data where applicable. A denied request returns 403. Authentication is still required for every registered endpoint through Better Auth session middleware.
Database schema generation
BARM exposes resource metadata through the Better Auth plugin schema. primaryKey() and unique() map to unique fields, references() maps to a foreign-key reference, and table() controls the model name. Database migration and adapter setup remain the responsibility of the host application.
Development
From the repository root:
pnpm install
pnpm typecheck
pnpm test
pnpm build
The test suite uses Vitest and covers pagination normalization, validation, generated fields, ownership, adapter calls, missing records, pagination boundaries, authorization rules, and endpoint registration.
GitHub Pages
This directory is a standalone GitHub Pages source. In the repository settings, select Pages, choose Deploy from a branch, select the default branch, and set the folder to /docs. GitHub Pages will build docs/index.md using the included Jekyll configuration and custom stylesheet.
For a repository named barm, the published site is:
https://p8labs.github.io/barm/
License
BARM is released under the MIT license.