skipToMainContentskipToFooter
NeuraAPI
NeuraAPI
Navigation
Architecture 25 août 2026 11 min de lecture

Pattern Circuit Breaker en Next.js : protéger vos appels DB et API

Partager :

Quand votre base de données ralentit, chaque requête qui attend aggrave la situation : les connexions s'accumulent, le pool sature, et tout l'application tombe en cascade. Le circuit breaker coupe ce cercle vicieux.

Le principe : trois états

  • Closed : tout va bien, les appels passent normalement.
  • Open : trop d'échecs récents — on échoue immédiatement sans même essayer d'appeler la ressource.
  • Half-open : après un délai de repos, on laisse passer un appel test pour vérifier si la ressource est revenue.

L'idée clé : quand une dépendance est malade, échouer vite vaut mieux qu'échouer lentement. Vos utilisateurs reçoivent une erreur claire en 50 ms au lieu de timeouts à 30 s, et la DB respire.

Pourquoi c'est critique avec Prisma + serverless

Sur Vercel, un pic de trafic pendant une panne de DB multiplie les fonctions qui attendent chacune une connexion. Résultat typique : « Too many connections » côté Postgres alors que le problème initial était un simple slow query. Le circuit breaker évite l'emballement.

Implémentation minimale en TypeScript

// src/lib/circuit-breaker.ts
type State = 'closed' | 'open' | 'half-open'

export class CircuitBreaker {
  private failures = 0
  private state: State = 'closed'
  private openedAt = 0

  constructor(
    private readonly threshold = 5,      // échecs avant ouverture
    private readonly resetTimeoutMs = 30_000, // repos avant half-open
  ) {}

  async exec<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - this.openedAt >= this.resetTimeoutMs) {
        this.state = 'half-open'
      } else {
        if (fallback) return fallback()
        throw new Error('circuit_open')
      }
    }

    try {
      const result = await fn()
      this.failures = 0
      this.state = 'closed'
      return result
    } catch (err) {
      this.failures++
      if (this.state === 'half-open' || this.failures >= this.threshold) {
        this.state = 'open'
        this.openedAt = Date.now()
      }
      if (fallback) return fallback()
      throw err
    }
  }
}

Application aux requêtes Prisma

// src/lib/db-guarded.ts
import { db } from './db'
import { CircuitBreaker } from './circuit-breaker'

const dbBreaker = new CircuitBreaker(5, 30_000)

export function safeQuery<T>(query: () => Promise<T>): Promise<T | null> {
  return dbBreaker.exec(query, () => null) // fallback : valeur par défaut
}

// Usage dans une page / route API :
const templates = await safeQuery(() =>
  db.template.findMany({ where: { published: true }, take: 20 }),
)

if (!templates) {
  // Dégradé gracieux : affichez du contenu en cache ou un message
}

Le fallback est ce qui distingue un circuit breaker utile d'un simple try/catch : l'application reste partiellement fonctionnelle pendant la panne.

Limite du pattern en serverless

En environnement serverless, l'état vit en mémoire par instance : chaque fonction Lambda garde son propre compteur. Trois solutions selon vos besoins :

  • Par instance : suffisant dans la plupart des cas — chaque instance se protège individuellement.
  • État partagé via Upstash Redis : compteur centralisé, cohérence globale.
  • Bibliothèque dédiée : Cockatiel en Node propose retry, timeout et breaker combinés.

Combiner avec timeout et retry

Le circuit breaker seul ne suffit pas : combinez-le toujours avec :

// Timeout + retry limité + breaker
async function resilientCall<T>(fn: () => Promise<T>): Promise<T | null> {
  return dbBreaker.exec(async () => {
    let lastErr: unknown
    for (let i = 0; i < 2; i++) {
      try {
        return await Promise.race([
          fn(),
          new Promise<never>((_, rej) =>
            setTimeout(() => rej(new Error('timeout')), 5000),
          ),
        ])
      } catch (err) { lastErr = err }
    }
    throw lastErr
  }, () => null)
}
  • Timeout court : 5 s max sur une query applicative.
  • Retry limité : 1–2 tentatives, jamais plus sur des erreurs système.
  • Breaker : coupe après N échecs consécutifs.

Checklist de mise en production

  • Loggez chaque transition d'état : open/half-open/closed sont vos meilleurs indicateurs de santé.
  • Seuils raisonnables : 5 échecs / 30 s de repos convient à la plupart des DB gérées.
  • Fallbacks explicites : décidez pour chaque query si null, cache ou erreur 503 est acceptable.
  • Ne mettez pas de breaker sur tout : réservez-le aux dépendances critiques (DB, API de paiement).

Une architecture résiliente, prête à l'emploi

Nos templates SaaS intègrent déjà safeQuery(), retries multi-providers et timeouts systématiques. Découvrir les templates

Conclusion

Le circuit breaker transforme une panne de dépendance d'un crash total en dégradation contrôlée. Une classe de 40 lignes, des seuils simples, et votre Next.js survivra à la prochaine indisponibilité de votre base de données. Cette résilience est incluse par défaut dans nos templates.