crudy
    FeaturesUse CasesDocumentationPricingAboutContact
    Get started free
    Documentation
    • Introduction
    • Installation
    • Quickstart
    • CLI Reference
    • Overview
    • Core Packages
    • App Structure
    • Modules & Routes
    • Module Anatomy
    • Backend Module
    • Frontend Module
    • Quotas
    • Full Example
    • Overview
    • Docker
    • Environment Variables
    • Licensing
    Architecture

    Modules & Routes

    How modules register pages, nav items, and routes.

    A module is the unit of feature you add to a Crudy app. The same package exports a backend half and a frontend half. The app wires the backend half in backend/server.ts and the frontend half in frontend/app/providers.tsx. The framework does the rest: enable/disable gating, license quota enforcement, route mounting, and navigation rendering.

    Backend routes

    BackendModule.routes is an array of BackendRouteDefinition:

    ts
    export interface BackendRouteDefinition {
        method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
        path: string;
        resource: string;
        permission: { activity: string; view: string } | null;
    }

    Each entry maps to a proxied data route. The framework registers the Fastify route at path, forwards to the PostgREST resource named resource, and enforces the morbac permission. A permission of null means any authenticated user; absent the registry, the route is denied.

    The full set of routes for a module looks like the one shipped by the documents module:

    ts
    export const documentRoutes = [
        { method: 'GET',    path: '/documents', resource: 'documents', permission: { activity: 'read',   view: 'documents' } },
        { method: 'POST',   path: '/documents', resource: 'documents', permission: { activity: 'create', view: 'documents' } },
        { method: 'PATCH',  path: '/documents', resource: 'documents', permission: { activity: 'update', view: 'documents' } },
        { method: 'DELETE', path: '/documents', resource: 'documents', permission: { activity: 'delete', view: 'documents' } },
    ];

    Before any of these routes runs, the framework checks that the module is enabled for the request's X-Org-Id both in app.org_modules and in the license. If either side denies, the request returns 404.

    Frontend routes

    FrontendModule.routes is an array of RouteDefinition:

    ts
    export type RouteDefinition =
        | { path: string; Component: ComponentType<ModulePageProps>; quota?: { key: string }; public?: false; breadcrumb?: BreadcrumbSegment[] }
        | { path: string; Component: ComponentType; public: true; breadcrumb?: BreadcrumbSegment[] };

    Each entry is mounted under the framework's catch-all app/[...slug]/page.tsx. Resolution is exact-match by path. There are no wildcards. A request to /tasks/123 does not resolve to a route whose path is /tasks; you register both explicitly if you need both.

    A non-public route receives ModulePageProps: the current token, the current user, the org list, the effective org filter, a checkPermission helper, and the host UI primitives (Button, Card, AppShell, etc.). The host primitives are how a module respects the app's theme and density without depending on @crudy/ui itself.

    A public route receives no props. Use it for unauthenticated pages like marketing or documentation.

    Navigation

    FrontendModule.navItems declares sidebar items:

    ts
    export interface NavItemDefinition {
        href: string;
        label: string;
        icon: ComponentType<{ className?: string }>;
        id?: string;
        hiddenByDefault?: boolean;
        divider?: boolean;
    }

    Items show up in the sidebar in module-registration order, alongside the framework's built-in items (organizations, users, roles, settings). The user can hide or reorder items per their preferences; hiddenByDefault ships the item in the hidden bucket by default.

    Catch-all resolution

    The framework's app/[...slug]/page.tsx reads the URL, looks up the matching route in the module registry, runs an authentication check unless public: true, runs the optional quota gate when quota is set, and renders the route's Component with ModulePageProps. If no module owns the path, the framework renders its 404 page.

    Always-present framework pages

    A small set of framework-owned pages do not live under any module: the home page, login, settings, organizations, users, roles, audit. They are registered in packages/frontend/src/core-routes.ts and are always available, regardless of which modules an app wires. Modules cannot disable them.

    PreviousApp StructureNext Module Anatomy
    crudy

    Build internal tools your teams actually use. Powered by a framework built for developers.

    Product

    • Features
    • Use Cases
    • Pricing

    Company

    • About
    • Contact

    © 2026 Crudy. All rights reserved.

    crudy.fr