Routes & Schemas

Shipped routes, hooks, collections, and the common consumer pattern for using thunder-core in your own code.

A quick map of everything thunder-core ships. As always, read the source files for exact request/response shapes and model fields.

Users, Me & Status

Route fileResponsibility
routes/users.tsAdmin user management wrapping better-auth admin APIs (create, update, ban, and read-only list/get/count).
routes/me.tsReturns the current authenticated user.
routes/home.tsHealth / status endpoint.

Shipped Hooks

HookPriorityResponsibility
hooks/essentials.tsHighestCORS (preflight + headers, allows X-TENANT-ID) and the DB-backed env fallback (Env.onGetFailed).
hooks/guard.tsVery highRBAC enforcement on every route via withAuthGuard.
hooks/requestLogs.tsLowestLogs every response at the appropriate level.

Schemas / Collections

All models use the framework's mongodb instance with a Zod schema. Read each schema file for exact fields, defaults, and unique indexes (scripts/syncDBIndexes.ts):

schemas/user.ts, schemas/session.ts, schemas/tenant.ts, schemas/tenantMember.ts, schemas/tenantInvite.ts, schemas/accessControl.ts, schemas/accessControlPolicy.ts, schemas/apiKey.ts, schemas/oauthClient.ts, schemas/oauthConsent.ts, schemas/jwk.ts, schemas/env.ts, schemas/references.ts, schemas/wallet.ts, schemas/walletLedger.ts.

resourceGrantSchema (exported from schemas/oauthConsent.ts) maps a tenant id to granted scopes, and is reused by both OAuth consents and API keys to scope access per tenant.

Common Consumer Pattern

Inside your own routes, gate access and read context like this:

routes/billing.ts
import { Router } from "@/core/http/router.ts";
import { withAuthSession } from "@/plugins/Huruf-Tech/thunder-core/utils/withAuthSession.ts";
import { withTenant } from "@/plugins/Huruf-Tech/thunder-core/utils/withTenant.ts";
import { Wallet } from "@/plugins/Huruf-Tech/thunder-core/lib/wallet.ts";

export default new Router("/api", function billing(router) {
  router.post("/charge", function charge() {
    return async (req) => {
      // Auth + tenant resolution (guard hook already enforced "billing.charge" permission)
      const { user } = await withAuthSession(req);
      const { tenant } = await withTenant(req);

      await Wallet.transfer({
        fromTenant: tenant._id,
        toTenant: platformTenantId,
        currency: "usd",
        amount: 500,
        user: user._id,
        purpose: "subscription",
      });

      return Response.ok();
    };
  });
}).group("Billing");

To make a new route's permission available, add its scope.action (router fn name + handler fn name) to an access-control policy and attach that policy to the appropriate role(s) - via the access-control routes or by editing scripts/syncAccessControl.ts.


On this page