Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tsyringe-rest-api-starter

Un starter minimal pour une API REST en classes (Node.js + Express

  • Prisma/PostgreSQL) entièrement câblée par injection de dépendances avec tsyringe. La gestion des utilisateurs sert d'exemple : l'objet du dépôt est l'architecture DI.

Le modèle mental

Ne fabrique pas tes dépendances - demande-les.

Aucune classe ne fait new de ses collaborateurs. Chaque classe déclare ses dépendances dans son constructeur, et un conteneur assemble le graphe. Un unique composition root (src/container.ts) déclare les bindings ; tout le reste ne dépend que d'abstractions.

Les choix du starter

  • Injection par constructeur sur trois couches : UserControllerUserServiceIUserRepository. Chaque couche ignore comment ses dépendances sont fabriquées.
  • On dépend d'une interface, pas d'une classe. UserService dépend du contrat IUserRepository via un injection token (Symbol), parce qu'une interface n'existe pas au runtime. Deux implémentations (PrismaUserRepository, InMemoryUserRepository) se branchent sur le même token : changer de stockage tient en une ligne.
  • PrismaClient en instance partagée (singleton) : un seul pool de connexions pour toute l'app. Le repository, sans état, est lui aussi en singleton.
  • Scope par requête via ContainerScoped : un conteneur enfant par requête HTTP (src/server.ts) donne à chaque requête un RequestContext isolé - l'emplacement naturel pour l'utilisateur authentifié, un identifiant de trace ou une transaction. Singleton fuiterait entre requêtes, transient ne partagerait rien : ContainerScoped est le seul correct.
  • Services testés en isolation, sans base : on injecte un faux repository via un conteneur enfant (user.service.test.ts). C'est le bénéfice n°1 de la DI, et la raison d'être de tout ce découplage.

Stack

Node 22 · TypeScript · Express 5 · Prisma 6 / PostgreSQL · tsyringe 4 · Vitest.

Démarrer

Prérequis : Node 22+, un PostgreSQL accessible.

npm install
cp .env.example .env          # puis ajuste DATABASE_URL
npx prisma migrate dev        # crée la table User
npm run dev                   # API sur http://localhost:3000

Frapper l'API :

curl -s localhost:3000/users
curl -s -X POST localhost:3000/users \
  -H 'Content-Type: application/json' \
  -d '{"email":"grace@hopper.dev","name":"Grace"}'

Lancer les tests (aucune base requise) :

npx vitest run

Ajouter une ressource

Le pattern se duplique mécaniquement. Pour une ressource Article :

  1. Modèle : ajoute model Article dans prisma/schema.prisma, puis npx prisma migrate dev.
  2. Contrat + token + implémentation : article.repository.ts avec une interface IArticleRepository, un token ARTICLE_REPOSITORY = Symbol("ArticleRepository"), et PrismaArticleRepository implements IArticleRepository qui injecte PrismaClient.
  3. Service : ArticleService (@injectable) qui injecte @inject(ARTICLE_REPOSITORY).
  4. Contrôleur : ArticleController (@injectable) qui injecte ArticleService.
  5. Binding : container.registerSingleton(ARTICLE_REPOSITORY, PrismaArticleRepository) dans le composition root.
  6. Routes : résous le contrôleur depuis req.scope, comme pour /users.

Aucune de ces étapes ne touche au code existant : c'est le point de la DI.

Volontairement hors périmètre

Ce starter est minimal. Pour un vrai service, on ajouterait :

  • Validation des entrées : un middleware validateRequest(schema) (ex. zod).
  • Middleware d'erreurs centralisé : mapper les erreurs vers des codes HTTP.
  • Validation des variables d'env au démarrage (fail-fast).
  • Logs structurés.
  • Arrêt gracieux : fermer le serveur et PrismaClient sur SIGTERM.
  • Authentification : RequestContext porterait l'utilisateur courant.

Structure

src/
  container.ts                     composition root (bindings du conteneur)
  server.ts                        Express + middleware de scope par requête
  http/request-context.ts          contexte ContainerScoped (1 par requête)
  modules/user/
    user.controller.ts             couche HTTP (classe injectée)
    user.service.ts                logique métier (dépend du contrat)
    user.repository.ts             contrat IUserRepository + token + impl Prisma
    in-memory-user.repository.ts   seconde implémentation du même contrat
    user.service.test.ts           test en isolation (faux repo via conteneur enfant)

Note : Vitest et les décorateurs

Vitest transpile par défaut avec esbuild, qui n'émet pas les métadonnées de décorateur dont tsyringe a besoin. Le starter branche SWC (vitest.config.ts) pour les générer - sans quoi @inject échouerait en test.

About

A minimal, opinionated starter for a class-based REST API wired with tsyringe — interface-based repositories, per-request container scope, services unit-tested in isolation.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages