Aller au contenu

Architecture

L'app web, l'API, le worker de tâches et le code qu'ils partagent vivent dans un seul dépôt Nx. Chaque requête suit le même chemin, et chaque partie a un seul rôle.

En bref

  • React et Vite devant, NestJS et GraphQL derrière, PostgreSQL et Redis en dessous.
  • Le travail lent part dans un worker, jamais dans une requête.
  • Les types de l'app web sont générés depuis le schéma de l'API : un champ renommé fait échouer le build.

Quelques paquets, un seul dépôt

Nx lance les tâches de chaque paquet et met leurs résultats en cache, et Yarn installe tout d'un coup. Des tags décident quel paquet peut importer lequel, et une règle de lint les fait respecter : l'app web n'importe jamais de code serveur.

  • Types et constantes partagés, comme les forfaits et les permissions, vivent dans un seul paquet
  • Les vérifications ne tournent à nouveau que sur les paquets touchés par une modification
Les paquets du dépôt
PaquetCe qu'il contient
app-frontL'app web : React 19, Vite, Apollo Client, Jotai, Linaria, Lingui
app-serverL'API et le worker de tâches : NestJS 11, GraphQL Yoga, TypeORM, PostgreSQL, Redis, BullMQ
app-sharedTypes, constantes et utilitaires pour les deux côtés : routes, forfaits, permissions, langues
app-uiLe design system : tokens, composants, icônes, Storybook
app-websiteCe site, prérendu, avec la documentation
app-lint-rulesLes 17 règles de lint maison
app-e2e-testingTests de bout en bout : un navigateur parcourt l'app
app-dockerFichiers Docker : services locaux et stack de production
app-utilsScripts : installation, yarn verify, production et sauvegardes

Le chemin de chaque requête

Chaque requête franchit les mêmes portes dans le même ordre : connexion, permission, saisie, puis un seul service. Rien n'atteint vos données avant leur accord à toutes.

  • Les resolvers restent légers : ils lisent le contexte de connexion et appellent un seul service
  • Les erreurs portent un code et un message traduit, jamais de détails internes
  1. L'app web envoie la requêteApollo ajoute le jeton de session Clerk et la langue de l'utilisateur.Authorization: Bearer <token>
  2. Un middleware identifie la requêteIl vérifie le jeton et remplace les identifiants Clerk par les vôtres.RequestContextMiddleware
  3. Les guards vérifient qui et quoiD'abord le guard d'authentification, puis la permission dont l'endpoint a besoin.WorkspacePermissionGuard('org:projects:create')
  4. La saisie est validéeUn pipe global vérifie chaque champ et refuse les champs inconnus.
  5. Un resolver léger appelle un seul serviceLe service porte les règles de la fonctionnalité.
  6. Le repository ajoute l'espace de travailChaque requête reste dans l'espace de travail du membre connecté.WorkspaceScopedRepository

Un worker pour le travail lent

Le worker est un processus à part qui exécute les tâches mises en file dans Redis avec BullMQ. Webhooks, changements de forfait et tâches planifiées passent par lui : les requêtes restent rapides et le travail en échec est relancé.

  • Une tâche ne transporte que des identifiants et charge elle-même des données à jour
  • Chaque tâche peut tourner deux fois, et en même temps que d'autres, sans risque
  • Les tâches planifiées (crons) s'écrivent comme des classes, et le worker les enregistre à son démarrage
  • Quand le worker s'arrête, les tâches en cours se terminent d'abord

Une stack choisie pièce par pièce

Chaque outil a sa raison d'être, et les conventions expliquent chacun d'eux : votre assistant et vous savez à quoi vous attendre avant de le changer.

Chaque partie de la stack, et sa raison d'être
PartieConstruit avecPourquoi
FrontendReact 19, TypeScript, Vite, Linaria, Jotai, Apollo Client, GraphQLDes builds rapides, des données typées et des styles sans coût à l'exécution.
BackendNestJS, GraphQL, PostgreSQL, TypeORM, Redis, BullMQUne API structurée, une base de données relationnelle et des tâches en arrière-plan.
ServicesClerk, Stripe, Docker, Caddy, SentryLa connexion et les paiements reposent sur des spécialistes ; la production, sur des conteneurs, avec HTTPS automatique et alertes d'erreur.
OutillageNx, oxlint, oxfmt, Jest, Vitest, PlaywrightUn seul dépôt, des vérifications rapides et des tests à tous les niveaux.

Questions

Pourquoi GraphQL ?

Un seul endpoint, un seul schéma, et des types TypeScript générés à partir de lui pour l'app web : un champ renommé fait échouer le build plutôt qu'un écran.

Pourquoi un seul schéma de base pour tous les espaces de travail ?

Un schéma par espace de travail n'est rentable que si vos clients créent leurs propres tables. Un seul schéma avec une colonne d'espace de travail, protégé par le repository dédié, garde les migrations simples.

Où ajouter ma propre fonctionnalité ?

Copiez la fonctionnalité Projets des deux côtés : elle montre chaque règle appliquée. Le guide « Add a feature » la parcourt étape par étape.

Démarrez votre prochaine app sur une base propre

Payez une fois et obtenez tout le code source, avec toutes les mises à jour futures.