Clean Architecture avec TypeScript + Bun
Ce tutoriel vous guide pas à pas pour créer une application simple en Clean Architecture avec TypeScript et Bun. Ce dernier est un runtime JavaScript/TypeScript rapide et moderne, compatible avec Node.js, qui offre une gestion des paquets intégrée et des performances supérieures.
Prérequis
- Bun installé sur votre machine (voir documentation officielle).
- Connaissances de base en TypeScript et
package.json.
Initialisation du Projet
Nous allons créer un workspace monorepo pour organiser notre projet selon les différentes couches de la Clean Architecture.
-
Créez un nouveau dossier pour votre projet avec la structure de dossiers souhaitée et initialisez-le avec Bun :
-
Ensuite, créer un sous-projet (qu'on appelle module) par couche de la Clean Architecture :
-
Ensuite, renseigner les sous-projets (ou modules) dans le
package.jsonà la racine du projet, dans la sectionworkspaces:{ "name": "clean-archi-ts-demo", "module": "index.ts", "type": "module", "private": true, "scripts": { "cli": "cd 04-interfaces && bun start" }, "workspaces": [ "01-entities", "02-use-cases", "03-adapters", "04-interfaces" ], "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5.9.3" } } -
Faire un
bun installpour vérifier que le workspace monorepo est bien initialisé. Cette commande installe les dépendances de tous les modules et crée les liens symboliques nécessaires entre eux. Donc, un seulbun installà la racine suffit. -
Vous devriez avoir la structure suivante:
racine/
├── 01-entities/
├── package.json
├── 02-use-cases/
├── package.json
├── 03-adapters/
├── package.json
├── 04-interfaces/
├── package.json
├── package.json
Implémentation des Couches
Entité (Domain)
-
Créez
01-entities/src/Member.ts: -
Dans
01-entities/index.ts, exportez l'entité :
Use Cases
Contient :
- La logique métier de l'application, orchestrant les interactions entre les entités et les interfaces.
- Les interfaces des
repositories(abstractions pour la persistance des données). Ces derniers seront implémentés dans la couche d'infrastructure.
Suivre les étapes suivantes pour implémenter la couche des Use Cases :
-
Importer les entités dans
02-use-cases/package.json: -
Lancer
bun installà la racine du projet pour que les dépendances soient résolues. -
Définir l'interface repository
02-use-cases/src/MemberRepository.ts: -
Définir le use case
02-use-cases/src/MemberUseCase.ts:import { Member } from "entities"; import type { MemberRepository } from "./MemberRepository"; import { randomUUIDv7 } from "bun"; export class MemberUseCase { constructor(private memberRepository: MemberRepository) {} async add(name: string, email: string): Promise<void> { const member = new Member(randomUUIDv7(), name, email); await this.memberRepository.save(member); } async getAll(): Promise<Member[]> { return await this.memberRepository.findAll(); } } -
Exporter le use case dans
02-use-cases/index.ts:
Adapters (Infrastructure)
Cette couche définit :
- L'implémentation concrète des interfaces de la couche des Use Cases, comme les repositories.
- L'implémentation des contrôleurs pour exposer les use cases via une API (REST, GraphQL, etc.).
Suivre les étapes suivantes pour implémenter la couche d'infrastructure :
-
Ajouter les dépendances nécessaires dans
03-adapters/package.json: -
Lancer
bun installà la racine du projet pour que les dépendances soient résolues. -
Créez
src/src/InMemoryMemberRepository.ts:import { Member } from "entities"; import type { MemberRepository } from "use-cases"; export class InMemoryMemberRepository implements MemberRepository { private members = new Map<string, Member>(); async save(member: Member): Promise<void> { this.members.set(member.uuid, member); } async findAll(): Promise<Member[]> { return Array.from(this.members.values()); } } -
Exportez le repository dans
src/index.ts:
Interfaces (Applications)
Cette couche contient les applications concrètes, comme le serveur express pour une API, ou la gestion des arguments en ligne de commande pour une application CLI.
-
Dans
04-interfaces/package.json, ajoutez les dépendances nécessaires et définir le script de démarrage :{ "name": "interfaces", "module": "index.ts", "type": "module", "private": true, "devDependencies": { "@types/bun": "latest" }, "scripts": { "start": "bun ./index.ts" }, "dependencies": { "entities": "../01-entities", "use-cases": "../02-use-cases", "adapters": "../03-adapters" }, "peerDependencies": { "typescript": "^5" } } -
Lancer
bun installà la racine du projet pour que les dépendances soient résolues. -
Dans
04-interfaces/index.ts, définir le contenu de l'application:import { InMemoryMemberRepository } from "adapters"; import { MemberUseCase } from "use-cases"; console.log("Welcome to member cli"); const memberRepository = new InMemoryMemberRepository(); const memberUseCase = new MemberUseCase(memberRepository); memberUseCase.add("Dupond", "dupond@mail.com"); memberUseCase.add("Durand", "durand@mail.com"); console.log("Members:", await memberUseCase.getAll()); -
On peut exécuter l'application directement avec Bun depuis le dossier
04-interfaces: -
On peut aussi définir un script de lancement dans le
package.jsonde la racine du projet pour exécuter l'application depuis la racine :{ "name": "clean-archi-ts-demo", "module": "index.ts", "type": "module", "private": true, "scripts": { "cli": "cd 04-interfaces && bun start" }, "workspaces": [ "01-entities", "02-use-cases", "03-adapters", "04-interfaces" ], "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5.9.3" } } -
On peut ainsi exécuter l'application depuis la racine du projet :
-
Le projet est disponible sur GitHub : clean-archi-ts-demo
Problèmes Potentiels
bun pm cache clean: Nettoie le cache de Bun, utile si vous rencontrez des problèmes de dépendances ou de build.bun pm cache rm -g: Supprime le cache global de Bun, ce qui peut résoudre des problèmes liés à des paquets globaux corrompus ou obsolètes.
Extensions Possibles
- Ajoutez un serveur HTTP avec Bun's built-in server pour exposer une API REST.
- Intégrez une vraie base de données (SQLite, PostgreSQL).
- Ajoutez des tests unitaires avec un framework comme Vitest.
- Implémentez plus de use cases (mise à jour, suppression de membres).
Ce tutoriel démontre les principes fondamentaux de la Clean Architecture avec TypeScript et Bun. Pour des applications plus complexes, considérez l'utilisation de frameworks comme NestJS pour faciliter l'injection de dépendances et la gestion des routes.