Skip to content

Frontend Architecture

granit-front is a pnpm monorepo containing the @granit/* packages — the JavaScript/TypeScript counterpart of the .NET framework granit-dotnet. Both frameworks expose symmetric contracts: TypeScript types in @granit/querying, @granit/data-exchange, @granit/workflow, etc. are the direct mirror of C# types in Granit.Querying, Granit.DataExchange, Granit.Workflow, etc.

PrincipleDescription
Source-directNo build step — packages export .ts files consumed directly by Vite
HeadlessPackages expose only hooks, types, and providers — UI components live in consuming applications
App-agnosticNo application-specific logic — only reusable abstractions
Peer dependenciesExternal dependencies are declared as peerDependencies, never dependencies
  • TypeScript 5 (strict) / React 19
  • Vitest 4 / ESLint 10 / Prettier 3
  • pnpm workspace / Node 24+
  • Conventional Commits via commitlint + husky
graph TD
    subgraph "Foundation layer"
        logger["@granit/logger"]
        utils["@granit/utils"]
        storage["@granit/storage"]
        cookies["@granit/cookies"]
    end

    subgraph "Infrastructure layer"
        api["@granit/api-client"]
        react-authn["@granit/react-authentication"]
        react-authz["@granit/react-authorization"]
        localization["@granit/localization"]
        logger-otlp["@granit/logger-otlp"]
        cookies-klaro["@granit/cookies-klaro"]
        tracing["@granit/tracing"]
        error-boundary["@granit/error-boundary"]
    end

    subgraph "Business layer"
        querying["@granit/querying"]
        data-exchange["@granit/data-exchange"]
        workflow["@granit/workflow"]
        timeline["@granit/timeline"]
        notifications["@granit/notifications"]
    end

    react-authn --> api
    localization --> storage
    logger-otlp --> logger
    cookies-klaro --> cookies
    error-boundary --> logger
    querying --> utils
    data-exchange --> utils
    timeline --> querying
    notifications --> querying

    utils -.-> clsx["clsx"]
    utils -.-> tw["tailwind-merge"]
    utils -.-> datefns["date-fns"]
    api -.-> axios["axios"]
    react-authn -.-> keycloak["keycloak-js"]
    querying -.-> tanstack["@tanstack/react-query"]
    data-exchange -.-> tanstack
    notifications -.-> signalr["@microsoft/signalr"]
    tracing -.-> otel["@opentelemetry/*"]
    logger-otlp -.-> otel
    cookies-klaro -.-> klaro["klaro"]
    localization -.-> i18next["i18next"]

    style logger fill:#e8f5e9
    style utils fill:#e8f5e9
    style storage fill:#e8f5e9
    style cookies fill:#e8f5e9
    style api fill:#e3f2fd
    style react-authn fill:#e3f2fd
    style react-authz fill:#e3f2fd
    style localization fill:#e3f2fd
    style logger-otlp fill:#e3f2fd
    style cookies-klaro fill:#e3f2fd
    style tracing fill:#e3f2fd
    style error-boundary fill:#e3f2fd
    style querying fill:#fff3e0
    style data-exchange fill:#fff3e0
    style workflow fill:#fff3e0
    style timeline fill:#fff3e0
    style notifications fill:#fff3e0

Legend:

  • Green (foundation) — packages with no internal @granit dependency
  • Blue (infrastructure) — packages depending on a foundation package
  • Orange (business) — packages implementing business functionality

Each business package follows a vertical slice structure:

packages/@granit/<package>/src/
types/ # TypeScript contracts (mirror of .NET types)
api/ # HTTP call functions (axios)
hooks/ # React hooks (business logic)
providers/ # React context providers
utils/ # Package-internal utilities
__tests__/ # Unit tests (Vitest)
index.ts # Single entry point (public re-exports)

This reflects the data flow:

types/ → api/ → hooks/ → providers/
  1. types/ defines the data contract (aligned with .NET backend)
  2. api/ encapsulates HTTP calls via axios
  3. hooks/ orchestrates data fetching (often via @tanstack/react-query) and exposes business logic
  4. providers/ supplies React context for consuming components

Foundation packages (logger, utils, storage, cookies) are simpler and do not necessarily have all these layers.

import { useQueryEndpoint } from '@granit/querying'
→ Vite alias → packages/@granit/querying/src/index.ts
→ TypeScript source → transpiled on the fly by Vite
→ no dist/, no intermediate build

This source-direct approach provides:

  • Instant HMR on framework code changes
  • No build watch to maintain for the monorepo
  • Direct source maps to the original code
  • Frictionless refactoring across framework and application

For Docker/CI builds, packages are compiled with tsup (ESM + .d.ts) and published to the GitHub Packages npm registry.

granit-dotnet (.NET)granit-front (TypeScript)
Granit.Querying@granit/querying
Granit.DataExchange.Export@granit/data-exchange (export)
Granit.DataExchange.Import@granit/data-exchange (import)
Granit.Workflow@granit/workflow
Granit.Notifications@granit/notifications
Granit.Timeline@granit/timeline
Controllers .NETEndpoints consumed by api/
ProblemDetailsProblemDetails in @granit/api-client
PagedResult<T>PagedResult<T> in @granit/api-client

TypeScript types in types/ of each package are the faithful mirror of C# DTOs on the backend. Any change to a .NET contract must be propagated to the corresponding TypeScript type, and vice versa.