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
| Paquet | Ce qu'il contient |
|---|---|
app-front | L'app web : React 19, Vite, Apollo Client, Jotai, Linaria, Lingui |
app-server | L'API et le worker de tâches : NestJS 11, GraphQL Yoga, TypeORM, PostgreSQL, Redis, BullMQ |
app-shared | Types, constantes et utilitaires pour les deux côtés : routes, forfaits, permissions, langues |
app-ui | Le design system : tokens, composants, icônes, Storybook |
app-website | Ce site, prérendu, avec la documentation |
app-lint-rules | Les 17 règles de lint maison |
app-e2e-testing | Tests de bout en bout : un navigateur parcourt l'app |
app-docker | Fichiers Docker : services locaux et stack de production |
app-utils | Scripts : 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
- L'app web envoie la requêteApollo ajoute le jeton de session Clerk et la langue de l'utilisateur.
Authorization: Bearer <token> - Un middleware identifie la requêteIl vérifie le jeton et remplace les identifiants Clerk par les vôtres.
RequestContextMiddleware - Les guards vérifient qui et quoiD'abord le guard d'authentification, puis la permission dont l'endpoint a besoin.
WorkspacePermissionGuard('org:projects:create') - La saisie est validéeUn pipe global vérifie chaque champ et refuse les champs inconnus.
- Un resolver léger appelle un seul serviceLe service porte les règles de la fonctionnalité.
- 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
ProcessStripeWebhookEventJobProcessClerkWebhookEventJobWebhookInboxRecoveryCronJobApplyWorkspaceMemberLimitJobProcessStripeWebhookEventJobUne 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.
| Partie | Construit avec | Pourquoi |
|---|---|---|
| Frontend | React 19, TypeScript, Vite, Linaria, Jotai, Apollo Client, GraphQL | Des builds rapides, des données typées et des styles sans coût à l'exécution. |
| Backend | NestJS, GraphQL, PostgreSQL, TypeORM, Redis, BullMQ | Une API structurée, une base de données relationnelle et des tâches en arrière-plan. |
| Services | Clerk, Stripe, Docker, Caddy, Sentry | La connexion et les paiements reposent sur des spécialistes ; la production, sur des conteneurs, avec HTTPS automatique et alertes d'erreur. |
| Outillage | Nx, oxlint, oxfmt, Jest, Vitest, Playwright | Un 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écouvrir les autres fonctionnalités
- Connexion et espaces de travailComptes Clerk, équipes et rôles vérifiés dans votre code.
- FacturationAbonnements Stripe par espace de travail, pilotés par webhooks.
- Garde-fous pour l'IAUn règlement, des règles de lint qui expliquent, et yarn verify.
- Design system70 composants, 466 icônes et des tokens pour le clair et le sombre.
- SécuritéEndpoints verrouillés, limites de débit, sauvegardes, alertes.
Démarrez votre prochaine app sur une base propre
Payez une fois et obtenez tout le code source, avec toutes les mises à jour futures.