Overview
How the pieces of a Crudy deployment fit together.
A Crudy deployment is three processes plus a license file. The framework owns most of the code; your app is a thin entry point that picks modules and exposes them through a backend and a frontend.
The pieces
- Backend. A Fastify server built by
createCrudyServer({ modules, appConfig }). Owns authentication, the management API, the data API proxy, audit logging, module wiring, and license enforcement. - Frontend. A Next.js App Router app wrapped in
CrudyProviders. Ships pre-built pages for login, settings, organizations, users, roles, and audit. Module pages plug into a catch-all route. - Database. PostgreSQL with the
pgmorbacextension and anappschema. Migrations are embedded in the framework packages (the morbac schema ships in@crudy/pgmorbac, the core tables in@crudy/backend) and are applied at boot together with any module and per-app (apps/<name>/migrations/) migrations. There is no separate database folder or init script; the backend creates roles, runs every migration, and re-applies grants on startup. - PostgREST. Generates a typed data API over the
appschema. It is never exposed publicly. The backend proxies/data/*requests to it and applies a permission check first. - License file. A signed token loaded from disk at boot. Declares the app name (audience), expiry, enabled modules, and per-org limits. Issued only by Crudy.
Request flow
The frontend never talks to PostgREST directly. All data requests go through the backend, where:
- Fastify authenticates the JWT or session cookie.
- The
DATA_PERMISSIONSregistry decides whether the method+path is allowed for an authenticated user, requires a morbac permission check, or is denied. - If a permission is required,
morbac.is_allowed(user, org, activity, view)runs against the database. The org id comes from theX-Org-Idheader. - The request is forwarded to PostgREST with the morbac context applied. PostgREST returns row-level-filtered results.
Authentication
Login uses the OAuth2 password grant at POST /oauth/token. The response includes a JWT signed with JWT_SECRET. Subsequent requests pass it as Authorization: Bearer <token> or via the session cookie set by the frontend.
API keys use the same endpoint with grant_type=client_credentials and a client_secret issued through the management UI.
Permissions
Crudy uses pgmorbac, a multi-org role-based access control extension. Activities (read, create, update, delete, ...) and views (orgs, users, documents, ...) are first-class records. Rules grant a user an activity on a view within an org. Roles bundle rules. Global rules grant unscoped access.
The same check, morbac.is_allowed(user, org, activity, view), runs on every protected backend route and on every row-level policy in PostgreSQL.
Licensing
The license is verified cryptographically once at boot. A cheap per-request guard then compares the in-memory expiry timestamp to the current time on every incoming request, and a timer self-terminates the process at expiry so the next boot trips the fail-closed check. There is no fallback path and no grace period. Module enablement and per-org quotas come out of the license file and are stacked on top of the database-level app.org_modules toggles. Both must allow for a module to run for an org.
Licenses are issued by Crudy. See Licensing for the consumer view, or request one at /pricing.
Modules
Modules are the unit of feature you add to an app. Each module ships a backend half and a frontend half through a single package. The backend half registers routes, migrations, morbac requirements and quotas. The frontend half registers pages, navigation items, settings tabs, and slot contributions. They are wired into an app from backend/server.ts and frontend/app/providers.tsx, and nowhere else.
Continue with Core Packages for what each @crudy/* package owns.