Skip to content

Architecture Overview

Grant is a multi-tenant RBAC platform organized as a TypeScript monorepo. It follows hexagonal architecture (ports and adapters) with strict layer boundaries and dependency inversion across packages.

System Overview

The platform ships two deployable applications and a set of shared packages:

ComponentDescription
Grant APIExpress server exposing both a GraphQL API (Apollo Server) and a REST API with OpenAPI / Swagger documentation
Web DashboardNext.js 15 (App Router) admin interface with i18n and permission-aware UI
PostgreSQLPrimary data store with Row-Level Security for tenant isolation
RedisOptional — caching, session storage, and job queues (BullMQ)
Object StorageOptional — file and image uploads via S3-compatible storage or local filesystem

Monorepo Packages

All shared code lives under packages/@grantjs. The dependency graph is a strict DAG — no circular imports are allowed.

PackageRole
SchemaGraphQL schema definitions, operation documents, and generated TypeScript types. Single source of truth for API contracts — types are imported from here, never redefined.
CoreDomain ports (ILogger, ICacheAdapter, IStorageAdapter, IEmailAdapter, IJobAdapter), the exception hierarchy (GrantExceptionNotFoundError, ValidationError, …), and the RBAC engine (Grant, PermissionChecker, ConditionEvaluator).
DatabaseDrizzle ORM schemas, relationships, migrations, and seed scripts. Accepts an optional ILogger via config.
ConstantsCanonical permission, role, and group definitions used by the database seeder and the authorization layer.
LoggerPino-based implementation of ILoggerFactory / ILogger from core.
ErrorsHttpException and mapDomainToHttp() — maps domain exceptions to HTTP status codes for the transport layer.
CacheMemory and Redis cache adapters implementing ICacheAdapter.
StorageLocal filesystem and S3 adapters implementing IFileStorageService.
EmailSMTP, SES, Mailgun, Mailjet, and console adapters implementing IEmailAdapter.
Jobsnode-cron and BullMQ adapters implementing IJobAdapter.
ClientBrowser SDK — React hooks and components for permission-based UI rendering.
ServerServer SDK — middleware guards for Express, Fastify, NestJS, and Next.js.
CLICLI tool for setup, authentication, and typings generation for @grantjs/server.

API Request Lifecycle

Every request — GraphQL or REST — flows through the same layered pipeline:

Layer responsibilities

LayerLocationAllowed dependencies
Transportgraphql/resolvers/ rest/routes/Handlers only — never services or repositories
Handlershandlers/Services only — never repositories
Servicesservices/Repositories and adapter ports (cache, email, storage, jobs)
Repositoriesrepositories/Database schemas from @grantjs/database only — never services or handlers

Composition root

Dependency injection is wired in middleware/context.middleware.ts. On every request the middleware:

  1. Creates repository, service, and handler instances
  2. Sets up a per-request RLS transaction context (when enabled)
  3. Builds the Grant RBAC engine instance via GrantService
  4. Attaches the full context — { grant, user, handlers, resourceResolvers, origin, locale } — to the request

Background jobs and startup tasks use a separate lib/app-context.lib.ts factory that builds the same object graph outside the HTTP lifecycle.

Multi-Tenancy Model

Grant uses account-based multi-tenancy. Each account is a fully isolated tenant with its own organizations, projects, and users:

Isolation is enforced at two layers:

  • Application layer — every query is scoped to the authenticated user's account via tenant-aware repositories
  • Database layer — PostgreSQL Row-Level Security policies on all pivot tables ensure isolation even if application logic is bypassed

See Multi-Tenancy for the full data model and scoping rules.

Permission Evaluation

The RBAC engine evaluates in this order: authenticate → resolve roles → check permissions → evaluate scope conditions. Conditions support attribute-based rules (e.g. "only if the user owns the resource") on top of the role-based model. See RBAC for the full permission model.

Technology Stack

Backend

TechnologyPurpose
Node.jsRuntime
TypeScriptType-safe development (strict mode)
ExpressHTTP framework
Apollo ServerGraphQL engine
Drizzle ORMType-safe database access and migrations
PostgreSQLPrimary database with RLS
RedisCaching, sessions, job queues
PinoStructured JSON logging
ZodRuntime schema validation

Frontend

TechnologyPurpose
Next.js 15React framework (App Router)
React 19UI library
TypeScriptType safety
Apollo ClientGraphQL data layer
Tailwind CSSUtility-first styling
next-intlInternationalization
ZustandClient state management
React Hook Form + ZodForm handling and validation

Infrastructure

TechnologyPurpose
Docker / Docker ComposeContainerization and local development
GitHub ActionsCI/CD pipeline

Design Principles

  1. Type safety across the stack — Types generated from the GraphQL schema (@grantjs/schema) are the single source of truth used by resolvers, handlers, services, repositories, and the web client.

  2. Hexagonal architecture — Domain logic depends only on ports defined in @grantjs/core. Infrastructure adapters (database, cache, storage, email, jobs, logging) are injected at the composition root and can be swapped without changing business logic.

  3. Security by default — Multi-tenant isolation via RLS, RBAC on every endpoint, bcrypt password hashing, JWT/JWKS signing with key rotation, and comprehensive audit logging.

  4. Strict layer boundaries — Each package has a single responsibility and explicit imports. Handlers never touch repositories; repositories never touch services. The composition root (context.middleware.ts) is the only place that wires layers together.


Next: Learn about Multi-Tenancy to understand how organizations and projects are isolated.

Released under the MIT License.