Frontend Architecture
Overview
Section titled “Overview”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.
Core principles
Section titled “Core principles”| Principle | Description |
|---|---|
| Source-direct | No build step — packages export .ts files consumed directly by Vite |
| Headless | Packages expose only hooks, types, and providers — UI components live in consuming applications |
| App-agnostic | No application-specific logic — only reusable abstractions |
| Peer dependencies | External dependencies are declared as peerDependencies, never dependencies |
Technical stack
Section titled “Technical stack”- TypeScript 5 (strict) / React 19
- Vitest 4 / ESLint 10 / Prettier 3
- pnpm workspace / Node 24+
- Conventional Commits via commitlint + husky
Dependency graph
Section titled “Dependency graph”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
@granitdependency - Blue (infrastructure) — packages depending on a foundation package
- Orange (business) — packages implementing business functionality
Vertical slice pattern
Section titled “Vertical slice pattern”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/- types/ defines the data contract (aligned with .NET backend)
- api/ encapsulates HTTP calls via
axios - hooks/ orchestrates data fetching (often via
@tanstack/react-query) and exposes business logic - providers/ supplies React context for consuming components
Foundation packages (logger, utils, storage, cookies) are simpler and
do not necessarily have all these layers.
Source resolution flow
Section titled “Source resolution flow”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 buildThis 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.
Alignment with granit-dotnet
Section titled “Alignment with granit-dotnet”| 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 .NET | Endpoints consumed by api/ |
ProblemDetails | ProblemDetails 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.
See also
Section titled “See also”- Frontend SDK Reference — package documentation
- Backend Architecture — .NET architecture overview
- Frontend Patterns — design patterns
- Frontend ADRs — architecture decision records