Hapta is a modular, scalable, and feature-rich backend framework designed to extend Pocketbase with authentication, schema validation, caching, and tenant-based service orchestration.
Designed to integrate easily into any modern Node.js backend β and purpose-built to unlock Pocketbase for production-scale deployments.
npm install hapta
# or
bun add hapta
# or
yarn add hapta-
β Authentication via Clover or Pocketbase Directly Multi-strategy login (OAuth, password, OTP, MFA) with tenant role/clearance support.
-
π Context-Based Auth (
ctx.principal) Automatically injects authenticated user state and metadata into each request. -
π¦ Zod-Based Schema Validation Auto-validates route inputs with fully typed schema files.
-
β‘ Smart Caching Layer Dynamic TTL, auto-invalidation, optimistic scaffolds, and memory-based caching.
-
π§± Modular Context System Unified
Contextclass handles services, request metadata, auth, and responses. -
ποΈ Batch Write Mode Queue DB changes, defer execution, and optimize complex flows.
/
βββ routes/
βββ auth/
βββ index.ts # Example auth endpoint
βββ schemas/
βββ auth/
βββ index.ts # Zod validation for auth route
hapta-config.json
- Note: Remove Clover_Tenant_ID if you want to handle login state yourself - disables clover authentication methods
{
"port": 8080,
"logLevel": "info",
"origin":"http://localhost:8081",
"AI_ENABLED": false,
"Clover_Tenant_ID": "",
"Clover_Secret":"",
"Clover_Server_Url":"clover.postlyapp.com",
"JWT_SECRET":"*",
"DatabaseUrl":"http://localhost:8080",
"ADMIN_EMAIL":"email@something.com",
"ADMIN_PASSWORD":""
}
if (ctx.principal.isAuthenticated) {
const username = ctx.principal.username;
const clearance = ctx.principal.highest_clearance;
}import Context from "hapta";
import { Database } from "hapta";
export default async function POST(ctx: Context, DB: Database) {
const { type } = ctx.metadata.query as any;
if (type === "oauth") {
const result = await ctx.services.Authenticate({ type: "oauth", ...ctx.metadata.query });
return result.isAuthenticated
? ctx.json(result)
: ctx.json({ error: true, message: "OAuth failed" }, 401);
} else if (type === "password") {
const result = await ctx.services.Clover.Authenticate({
type: "passwordAuth",
...ctx.metadata.json
});
return result.isAuthenticated
? ctx.json(result)
: ctx.json({ error: true, message: "Invalid credentials" }, 400);
}
return ctx.json({ error: true, message: "Unsupported auth type" }, 400);
}bun --hot run haptaimport { DatabaseService } from "hapta";
const DB = new DatabaseService(pocketbase, cacheHandler);const record = await DB.get("users", "abc123");await DB.list("posts", { page: 1, limit: 20 });DB.setBatch(true);
await DB.create("comments", { body: "Hello!" }, true);
await DB.saveChanges();ctx.json({ success: true }, 200);
ctx.html("<h1>Welcome</h1>");
ctx.text("Hello world");All responses include CORS headers out of the box:
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type, Authorization// /schemas/auth/index.ts
import { z } from "zod";
export default z.object({
emailOrUsername: z.string().min(3),
password: z.string().min(6),
});Hapta automatically loads and validates this schema for /auth requests before your handler runs.
DB.setBatch(true);
await DB.create("logs", { message: "Init" });
await DB.update("users", "abc", { active: false });
await DB.delete("sessions", "xyz");
await DB.saveChanges(); // executes all queued ops| Method | Caches | TTL | Invalidation |
|---|---|---|---|
get() |
β | dynamic | on update, delete |
list() |
β | dynamic | on create, update, delete |
create() |
β | β | invalidates all list caches |
update() |
β | β | invalidates list + get cache |
delete() |
β | β | invalidates list + get cache |
You can wire Hapta into any server environment:
const ctx = new Context();
// inject principal, services, metadata, etc.
ctx.metadata = {
requestID: "xyz",
timestamp: new Date(),
json: await req.json(),
headers: req.headers,
...
};
// call route handler
const res = await handler(ctx, DB);
return res;MIT Β© Postr-Inc β Built for scale.
- Open an issue
- PRs welcome
- Built with β€οΈ by the Postr-Inc team