<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Godwill Oussou</title>
    <description>The latest articles on DEV Community by Godwill Oussou (@godwill).</description>
    <link>https://dev.to/godwill</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1316829%2F4d06b07f-2b2d-45ff-b4b6-c2943753d1ce.png</url>
      <title>DEV Community: Godwill Oussou</title>
      <link>https://dev.to/godwill</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/godwill"/>
    <language>en</language>
    <item>
      <title>title: "Le problème N+1 en GraphQL — pourquoi votre API Laravel ralentit (et comment le régler avec Lighthouse)"</title>
      <dc:creator>Godwill Oussou</dc:creator>
      <pubDate>Mon, 13 Jul 2026 22:18:02 +0000</pubDate>
      <link>https://dev.to/godwill/title-le-probleme-n1-en-graphql-pourquoi-votre-api-laravel-ralentit-et-comment-le-regler-avec-nmn</link>
      <guid>https://dev.to/godwill/title-le-probleme-n1-en-graphql-pourquoi-votre-api-laravel-ralentit-et-comment-le-regler-avec-nmn</guid>
      <description>&lt;p&gt;Vous avez branché GraphQL sur votre API Laravel. Tout fonctionne, les requêtes sont propres, vos développeurs front-end sont contents de ne demander que les champs dont ils ont besoin. Puis un jour, une liste qui contenait 10 éléments en contient 500 — et votre API qui répondait en 80ms se met à répondre en 4 secondes.&lt;/p&gt;

&lt;p&gt;Neuf fois sur dix, la cause est la même : le problème N+1. Et la bonne nouvelle, c'est qu'il se corrige en quelques minutes une fois qu'on sait où regarder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduire le problème
&lt;/h2&gt;

&lt;p&gt;Prenons un cas simple : une liste d'articles, chacun avec son auteur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;titre&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;auteur&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rien d'alarmant à première vue. Pourtant, si la relation &lt;code&gt;auteur&lt;/code&gt; est résolue naïvement — un accessor PHP qui recharge la relation à chaque fois, ou un resolver custom mal écrit — voici ce qui se passe réellement côté base de données :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;-- 1 requête&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;-- pour l'article 1&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;-- pour l'article 2&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;-- pour l'article 3&lt;/span&gt;
&lt;span class="c1"&gt;-- ... répété pour chacun des 20 articles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;21 requêtes SQL pour une seule query GraphQL.&lt;/strong&gt; Multipliez ça par une liste de 500 éléments, ou par deux niveaux de relations imbriquées (articles → commentaires → auteurs des commentaires), et vous comprenez pourquoi votre temps de réponse explose sans qu'aucune ligne de votre code applicatif n'ait visiblement changé.&lt;/p&gt;

&lt;p&gt;C'est le problème &lt;strong&gt;N+1&lt;/strong&gt; : une requête initiale (le "1") suivie d'une requête supplémentaire pour &lt;em&gt;chaque&lt;/em&gt; élément de la liste (le "N").&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi ça arrive si facilement en GraphQL
&lt;/h2&gt;

&lt;p&gt;En REST, on écrit généralement un &lt;code&gt;with('auteur')&lt;/code&gt; explicite dans le contrôleur, et le problème saute aux yeux dans le code. En GraphQL, la relation est déclarée une fois dans le schéma et résolue automatiquement pour chaque requête reçue — ce qui est justement tout l'intérêt du langage. Mais ça veut dire que le risque de N+1 est &lt;strong&gt;structurel&lt;/strong&gt;, pas accidentel : dès qu'un champ expose une relation sur une liste, le danger existe par défaut.&lt;/p&gt;

&lt;h2&gt;
  
  
  La solution : regrouper avant de résoudre
&lt;/h2&gt;

&lt;p&gt;Le principe qui règle 100% des cas de N+1, quel que soit le langage ou le framework, tient en une phrase :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ne jamais exécuter une requête à l'intérieur d'une boucle. Collecter d'abord toutes les clés nécessaires, puis résoudre en un seul appel groupé.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Avec &lt;a href="https://lighthouse-php.com" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; (le package GraphQL le plus utilisé dans l'écosystème Laravel), les directives de relation standard implémentent déjà ce principe pour vous :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;titre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;auteur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Utilisateur&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;belongsTo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En interne, &lt;code&gt;@belongsTo&lt;/code&gt; ne va &lt;strong&gt;pas&lt;/strong&gt; charger l'auteur article par article. Elle collecte les 20 &lt;code&gt;user_id&lt;/code&gt; de la page en cours d'exécution, puis exécute une seule requête groupée juste avant de construire la réponse :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2 requêtes SQL au total, quelle que soit la taille de la liste.&lt;/strong&gt; C'est le comportement par défaut de Lighthouse dès que vous utilisez les directives de relation standard (&lt;code&gt;@hasMany&lt;/code&gt;, &lt;code&gt;@belongsTo&lt;/code&gt;, &lt;code&gt;@belongsToMany&lt;/code&gt;) — aucune configuration supplémentaire n'est nécessaire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Et pour un champ calculé qui n'est pas une relation Eloquent ?
&lt;/h2&gt;

&lt;p&gt;Le vrai piège arrive quand vous exposez un champ calculé — par exemple une note moyenne, un nombre de vues, ou tout ce qui nécessite une agrégation sur une autre table. Un resolver naïf ressemblera à ça :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResolveNoteMoyenne&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Article&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ❌ Une requête AVG() par article affiché dans la liste&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Avis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'article_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'note'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le même problème N+1 revient, simplement déplacé hors des relations Eloquent standard. La solution : implémenter vous-même le pattern de batching avec un &lt;code&gt;BatchLoader&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NoteMoyenneLoader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BatchLoader&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Un seul appel groupé pour tous les articles en attente&lt;/span&gt;
        &lt;span class="nv"&gt;$moyennes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Avis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whereIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'article_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;selectRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'article_id, AVG(note) as moyenne'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'article_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'moyenne'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'article_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$resultats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$resultats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$moyennes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$resultats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResolveNoteMoyenne&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Article&lt;/span&gt; &lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;NoteMoyenneLoader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$article&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le résultat est identique à celui des directives natives : une seule requête &lt;code&gt;GROUP BY&lt;/code&gt; pour toute la liste, peu importe qu'elle contienne 5 ou 5000 articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment détecter un N+1 avant qu'il n'atteigne la production
&lt;/h2&gt;

&lt;p&gt;Trois réflexes suffisent :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Laravel Debugbar&lt;/strong&gt; en développement : il affiche le nombre exact de requêtes SQL exécutées par requête GraphQL. Si ce nombre grandit avec la taille de la liste retournée, vous avez un N+1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DB::listen()&lt;/code&gt;&lt;/strong&gt; dans vos tests d'intégration, combiné à une assertion sur le nombre de requêtes exécutées.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel Telescope&lt;/strong&gt; pour une vue détaillée, avec l'origine (stack trace) de chaque requête.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La règle pratique : le nombre de requêtes SQL d'une query GraphQL ne devrait &lt;strong&gt;jamais&lt;/strong&gt; dépendre de la taille de la liste qu'elle retourne. Si c'est le cas, cherchez la relation ou le champ calculé qui n'utilise pas encore le batching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce n'est qu'un point de départ
&lt;/h2&gt;

&lt;p&gt;Le N+1 est souvent le premier problème de performance qu'on rencontre avec GraphQL — mais ce n'est qu'un point de départ. Vient ensuite la question du cache (que mettre en cache, et surtout quand l'invalider), la limitation de la complexité des requêtes pour se protéger des abus, et le rate limiting pour ne pas se faire déborder par un client mal codé ou malveillant.&lt;/p&gt;

&lt;p&gt;J'ai rassemblé tout ça — N+1, cache Redis, rate limiting, scaling, plus la construction complète d'une API GraphQL avec Laravel et sa consommation depuis un client Angular — dans une formation complète : &lt;strong&gt;[GraphQL avec Laravel]&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si cet article vous a fait gagner du temps, la formation devrait vous en faire gagner beaucoup plus.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Des questions sur le N+1 ou sur GraphQL avec Laravel ? Les commentaires sont ouverts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>graphql</category>
      <category>php</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
