[{"data":1,"prerenderedAt":894},["ShallowReactive",2],{"page-\u002Fapi\u002Fcacher-une-cle-d-api":3,"pieces-api":851,"autour-\u002Fapi\u002Fcacher-une-cle-d-api":876,"prerequis-\u002Fapi\u002Fcacher-une-cle-d-api":881,"plan-api":883},{"id":4,"title":5,"body":6,"description":816,"extension":817,"ligne":818,"meta":819,"motsCles":820,"navigation":399,"niveau":825,"path":826,"prerequis":827,"ressources":829,"seo":840,"sources":841,"stem":849,"__hash__":850},"docs\u002Fapi\u002F04.cacher-une-cle-d-api.md","Cacher une clé d'API",{"type":7,"value":8,"toc":805},"minimark",[9,14,32,36,48,51,160,163,169,173,192,208,212,223,296,318,332,345,349,450,460,464,522,528,532,535,559,736,739,743,761,765,801],[10,11,13],"h2",{"id":12},"ce-que-tu-vas-apprendre","Ce que tu vas apprendre",[15,16,17,21,29],"ul",{},[18,19,20],"li",{},"Pourquoi une clé placée dans le navigateur est une clé perdue.",[18,22,23,24,28],{},"Déclarer et lire un secret avec ",[25,26,27],"code",{},"runtimeConfig",".",[18,30,31],{},"Faire passer l'appel par ta propre route, qui joue le rôle d'intermédiaire.",[10,33,35],{"id":34},"le-problème","Le problème",[37,38,39,40,44,45,28],"p",{},"Une ",[41,42,43],"strong",{},"clé d'API"," est un mot de passe qui identifie ton application auprès d'un service, souvent payant ou limité. Imagine une API de photos de plats qui demande une clé dans l'en-tête ",[25,46,47],{},"Authorization",[37,49,50],{},"La tentation : appeler directement le service depuis ta page.",[52,53,59],"pre",{"className":54,"code":55,"filename":56,"language":57,"meta":58,"style":58},"language-vue shiki shiki-themes github-light-default github-dark-default github-dark-default","\u003Cscript setup lang=\"ts\">\n\u002F\u002F MAUVAIS : cette clé part dans le navigateur de chaque visiteur\nconst { data } = await useFetch('https:\u002F\u002Fapi.exemple-photos.test\u002Fplats', {\n  headers: { Authorization: 'Bearer ma-cle-secrete' }\n})\n\u003C\u002Fscript>\n","app\u002Fpages\u002Frecettes\u002Findex.vue","vue","",[25,60,61,91,98,132,144,150],{"__ignoreMap":58},[62,63,66,70,74,78,81,84,88],"span",{"class":64,"line":65},"line",1,[62,67,69],{"class":68},"scfrF","\u003C",[62,71,73],{"class":72},"s5JFL","script",[62,75,77],{"class":76},"s5iOY"," setup",[62,79,80],{"class":76}," lang",[62,82,83],{"class":68},"=",[62,85,87],{"class":86},"shWOn","\"ts\"",[62,89,90],{"class":68},">\n",[62,92,94],{"class":64,"line":93},2,[62,95,97],{"class":96},"sd6ml","\u002F\u002F MAUVAIS : cette clé part dans le navigateur de chaque visiteur\n",[62,99,101,105,108,111,114,116,119,123,126,129],{"class":64,"line":100},3,[62,102,104],{"class":103},"sOnC1","const",[62,106,107],{"class":68}," { ",[62,109,110],{"class":76},"data",[62,112,113],{"class":68}," } ",[62,115,83],{"class":103},[62,117,118],{"class":103}," await",[62,120,122],{"class":121},"syCW7"," useFetch",[62,124,125],{"class":68},"(",[62,127,128],{"class":86},"'https:\u002F\u002Fapi.exemple-photos.test\u002Fplats'",[62,130,131],{"class":68},", {\n",[62,133,135,138,141],{"class":64,"line":134},4,[62,136,137],{"class":68},"  headers: { Authorization: ",[62,139,140],{"class":86},"'Bearer ma-cle-secrete'",[62,142,143],{"class":68}," }\n",[62,145,147],{"class":64,"line":146},5,[62,148,149],{"class":68},"})\n",[62,151,153,156,158],{"class":64,"line":152},6,[62,154,155],{"class":68},"\u003C\u002F",[62,157,73],{"class":72},[62,159,90],{"class":68},[37,161,162],{},"Tout ce qui s'exécute dans le navigateur est lisible : code envoyé, onglet Réseau, en-têtes de requête. N'importe quel visiteur copie la clé et l'utilise à tes frais.",[164,165,166],"caution",{},[37,167,168],{},"Même placée dans une variable d'environnement, une clé lue côté navigateur reste visible. Ce qui compte, c'est où le code s'exécute, pas où tu as rangé la valeur.",[10,170,172],{"id":171},"la-solution-le-serveur-appelle-pas-le-navigateur","La solution : le serveur appelle, pas le navigateur",[37,174,175,176,179,180,183,184,187,188,191],{},"Le code de ",[25,177,178],{},"server\u002F"," ne quitte jamais le serveur. L'idée : ton navigateur appelle ",[41,181,182],{},"ta"," route (",[25,185,186],{},"\u002Fapi\u002Fphotos","), et c'est cette route qui appelle le service avec la clé. On parle d'",[41,189,190],{},"intermédiaire"," (ou proxy).",[193,194,195,202,205],"ol",{},[18,196,197,198,201],{},"Le navigateur envoie ",[25,199,200],{},"GET \u002Fapi\u002Fphotos"," à ton serveur.",[18,203,204],{},"Ta route ajoute la clé et appelle le service externe.",[18,206,207],{},"Elle renvoie la réponse au navigateur, sans la clé.",[10,209,211],{"id":210},"étape-1-déclarer-le-secret","Étape 1 : déclarer le secret",[37,213,214,215,218,219,222],{},"Dans ",[25,216,217],{},"nuxt.config.ts",", une clé hors de ",[25,220,221],{},"public"," reste côté serveur :",[52,224,228],{"className":225,"code":226,"filename":217,"language":227,"meta":58,"style":58},"language-ts shiki shiki-themes github-light-default github-dark-default github-dark-default","export default defineNuxtConfig({\n  runtimeConfig: {\n    cleExemple: '', \u002F\u002F secrète : serveur seulement\n    public: {\n      nomSite: 'Mes recettes' \u002F\u002F publique : visible partout\n    }\n  }\n})\n","ts",[25,229,230,245,250,264,269,280,285,291],{"__ignoreMap":58},[62,231,232,235,238,241],{"class":64,"line":65},[62,233,234],{"class":103},"export",[62,236,237],{"class":103}," default",[62,239,240],{"class":121}," defineNuxtConfig",[62,242,244],{"class":243},"sIXEE","({\n",[62,246,247],{"class":64,"line":93},[62,248,249],{"class":68},"  runtimeConfig: {\n",[62,251,252,255,258,261],{"class":64,"line":100},[62,253,254],{"class":68},"    cleExemple: ",[62,256,257],{"class":86},"''",[62,259,260],{"class":68},", ",[62,262,263],{"class":96},"\u002F\u002F secrète : serveur seulement\n",[62,265,266],{"class":64,"line":134},[62,267,268],{"class":68},"    public: {\n",[62,270,271,274,277],{"class":64,"line":146},[62,272,273],{"class":68},"      nomSite: ",[62,275,276],{"class":86},"'Mes recettes'",[62,278,279],{"class":96}," \u002F\u002F publique : visible partout\n",[62,281,282],{"class":64,"line":152},[62,283,284],{"class":68},"    }\n",[62,286,288],{"class":64,"line":287},7,[62,289,290],{"class":68},"  }\n",[62,292,294],{"class":64,"line":293},8,[62,295,149],{"class":243},[37,297,298,299,302,303,306,307,310,311,314,315,28],{},"La valeur réelle va dans ",[25,300,301],{},".env",". Le nom suit la règle de la documentation : majuscules, préfixe ",[25,304,305],{},"NUXT_",", mots séparés par ",[25,308,309],{},"_"," aux changements de casse. ",[25,312,313],{},"cleExemple"," devient ",[25,316,317],{},"NUXT_CLE_EXEMPLE",[52,319,323],{"className":320,"code":321,"filename":301,"language":322,"meta":58,"style":58},"language-ini shiki shiki-themes github-light-default github-dark-default github-dark-default","NUXT_CLE_EXEMPLE=la-vraie-cle\n","ini",[25,324,325],{"__ignoreMap":58},[62,326,327,329],{"class":64,"line":65},[62,328,317],{"class":103},[62,330,331],{"class":68},"=la-vraie-cle\n",[333,334,335],"warning",{},[37,336,337,338,340,341,344],{},"La variable doit aussi être déclarée dans ",[25,339,27],{},", sinon elle est ignorée. Et n'utilise jamais ",[25,342,343],{},"NUXT_PUBLIC_"," pour un secret : tout ce qui est public est envoyé au navigateur.",[10,346,348],{"id":347},"étape-2-la-route-intermédiaire","Étape 2 : la route intermédiaire",[52,350,353],{"className":225,"code":351,"filename":352,"language":227,"meta":58,"style":58},"export default defineEventHandler(async (event) => {\n  const config = useRuntimeConfig(event)\n\n  return await $fetch('https:\u002F\u002Fapi.exemple-photos.test\u002Fplats', {\n    headers: { Authorization: `Bearer ${config.cleExemple}` }\n  })\n})\n","server\u002Fapi\u002Fphotos.get.ts",[25,354,355,378,395,401,417,437,442],{"__ignoreMap":58},[62,356,357,359,361,364,366,369,372,375],{"class":64,"line":65},[62,358,234],{"class":103},[62,360,237],{"class":103},[62,362,363],{"class":121}," defineEventHandler",[62,365,125],{"class":243},[62,367,368],{"class":103},"async",[62,370,371],{"class":243}," (event) ",[62,373,374],{"class":103},"=>",[62,376,377],{"class":68}," {\n",[62,379,380,383,386,389,392],{"class":64,"line":93},[62,381,382],{"class":103},"  const",[62,384,385],{"class":76}," config",[62,387,388],{"class":103}," =",[62,390,391],{"class":121}," useRuntimeConfig",[62,393,394],{"class":68},"(event)\n",[62,396,397],{"class":64,"line":100},[62,398,400],{"emptyLinePlaceholder":399},true,"\n",[62,402,403,406,408,411,413,415],{"class":64,"line":134},[62,404,405],{"class":103},"  return",[62,407,118],{"class":103},[62,409,410],{"class":121}," $fetch",[62,412,125],{"class":68},[62,414,128],{"class":86},[62,416,131],{"class":68},[62,418,419,422,425,428,430,432,435],{"class":64,"line":146},[62,420,421],{"class":68},"    headers: { Authorization: ",[62,423,424],{"class":86},"`Bearer ${",[62,426,427],{"class":68},"config",[62,429,28],{"class":86},[62,431,313],{"class":68},[62,433,434],{"class":86},"}`",[62,436,143],{"class":68},[62,438,439],{"class":64,"line":152},[62,440,441],{"class":68},"  })\n",[62,443,444,447],{"class":64,"line":287},[62,445,446],{"class":68},"}",[62,448,449],{"class":243},")\n",[37,451,452,455,456,459],{},[25,453,454],{},"useRuntimeConfig(event)"," reçoit l'",[25,457,458],{},"event"," : la documentation le recommande dans les routes serveur pour appliquer les variables d'environnement au moment de l'exécution. Le service reçoit la clé, ton visiteur jamais.",[10,461,463],{"id":462},"étape-3-la-page-appelle-ta-route","Étape 3 : la page appelle ta route",[52,465,467],{"className":54,"code":466,"filename":56,"language":57,"meta":58,"style":58},"\u003Cscript setup lang=\"ts\">\nconst { data: photos } = await useFetch('\u002Fapi\u002Fphotos')\n\u003C\u002Fscript>\n",[25,468,469,485,514],{"__ignoreMap":58},[62,470,471,473,475,477,479,481,483],{"class":64,"line":65},[62,472,69],{"class":68},[62,474,73],{"class":72},[62,476,77],{"class":76},[62,478,80],{"class":76},[62,480,83],{"class":68},[62,482,87],{"class":86},[62,484,90],{"class":68},[62,486,487,489,491,493,496,499,501,503,505,507,509,512],{"class":64,"line":93},[62,488,104],{"class":103},[62,490,107],{"class":68},[62,492,110],{"class":243},[62,494,495],{"class":68},": ",[62,497,498],{"class":76},"photos",[62,500,113],{"class":68},[62,502,83],{"class":103},[62,504,118],{"class":103},[62,506,122],{"class":121},[62,508,125],{"class":68},[62,510,511],{"class":86},"'\u002Fapi\u002Fphotos'",[62,513,449],{"class":68},[62,515,516,518,520],{"class":64,"line":100},[62,517,155],{"class":68},[62,519,73],{"class":72},[62,521,90],{"class":68},[37,523,524,525,527],{},"Dans l'onglet Réseau, tu ne vois que ",[25,526,186],{},". Aucune clé, aucune adresse du service externe.",[10,529,531],{"id":530},"aller-plus-loin-avec-la-route","Aller plus loin avec la route",[37,533,534],{},"L'intermédiaire apporte plus que le secret. Tu peux :",[15,536,537,547,553],{},[18,538,539,542,543,546],{},[41,540,541],{},"filtrer"," : ne renvoyer que les champs utiles, comme avec ",[25,544,545],{},"transform"," ;",[18,548,549,552],{},[41,550,551],{},"valider"," : n'accepter que les paramètres que tu attends, au lieu de les relayer tels quels ;",[18,554,555,558],{},[41,556,557],{},"protéger"," : refuser les requêtes non autorisées avant de dépenser le quota du service.",[52,560,562],{"className":225,"code":561,"filename":352,"language":227,"meta":58,"style":58},"export default defineEventHandler(async (event) => {\n  const config = useRuntimeConfig(event)\n  const { plat } = getQuery(event)\n\n  if (typeof plat !== 'string' || plat.length > 50) {\n    throw createError({ status: 400, statusText: 'Parametre plat invalide' })\n  }\n\n  return await $fetch('https:\u002F\u002Fapi.exemple-photos.test\u002Fplats', {\n    headers: { Authorization: `Bearer ${config.cleExemple}` },\n    query: { nom: plat }\n  })\n})\n",[25,563,564,582,594,612,616,654,677,681,685,700,718,724,729],{"__ignoreMap":58},[62,565,566,568,570,572,574,576,578,580],{"class":64,"line":65},[62,567,234],{"class":103},[62,569,237],{"class":103},[62,571,363],{"class":121},[62,573,125],{"class":243},[62,575,368],{"class":103},[62,577,371],{"class":243},[62,579,374],{"class":103},[62,581,377],{"class":68},[62,583,584,586,588,590,592],{"class":64,"line":93},[62,585,382],{"class":103},[62,587,385],{"class":76},[62,589,388],{"class":103},[62,591,391],{"class":121},[62,593,394],{"class":68},[62,595,596,598,600,603,605,607,610],{"class":64,"line":100},[62,597,382],{"class":103},[62,599,107],{"class":68},[62,601,602],{"class":76},"plat",[62,604,113],{"class":68},[62,606,83],{"class":103},[62,608,609],{"class":121}," getQuery",[62,611,394],{"class":68},[62,613,614],{"class":64,"line":134},[62,615,400],{"emptyLinePlaceholder":399},[62,617,618,621,624,627,630,633,636,639,642,645,648,651],{"class":64,"line":146},[62,619,620],{"class":103},"  if",[62,622,623],{"class":68}," (",[62,625,626],{"class":103},"typeof",[62,628,629],{"class":68}," plat ",[62,631,632],{"class":103},"!==",[62,634,635],{"class":86}," 'string'",[62,637,638],{"class":103}," ||",[62,640,641],{"class":68}," plat.",[62,643,644],{"class":76},"length",[62,646,647],{"class":103}," >",[62,649,650],{"class":76}," 50",[62,652,653],{"class":68},") {\n",[62,655,656,659,662,665,668,671,674],{"class":64,"line":152},[62,657,658],{"class":103},"    throw",[62,660,661],{"class":121}," createError",[62,663,664],{"class":68},"({ status: ",[62,666,667],{"class":76},"400",[62,669,670],{"class":68},", statusText: ",[62,672,673],{"class":86},"'Parametre plat invalide'",[62,675,676],{"class":68}," })\n",[62,678,679],{"class":64,"line":287},[62,680,290],{"class":68},[62,682,683],{"class":64,"line":293},[62,684,400],{"emptyLinePlaceholder":399},[62,686,688,690,692,694,696,698],{"class":64,"line":687},9,[62,689,405],{"class":103},[62,691,118],{"class":103},[62,693,410],{"class":121},[62,695,125],{"class":68},[62,697,128],{"class":86},[62,699,131],{"class":68},[62,701,703,705,707,709,711,713,715],{"class":64,"line":702},10,[62,704,421],{"class":68},[62,706,424],{"class":86},[62,708,427],{"class":68},[62,710,28],{"class":86},[62,712,313],{"class":68},[62,714,434],{"class":86},[62,716,717],{"class":68}," },\n",[62,719,721],{"class":64,"line":720},11,[62,722,723],{"class":68},"    query: { nom: plat }\n",[62,725,727],{"class":64,"line":726},12,[62,728,441],{"class":68},[62,730,732,734],{"class":64,"line":731},13,[62,733,446],{"class":68},[62,735,449],{"class":243},[37,737,738],{},"Ne relaie jamais une entrée du visiteur sans la vérifier : le chapitre 5 détaille la validation.",[10,740,742],{"id":741},"et-si-la-clé-a-fuité","Et si la clé a fuité ?",[15,744,745,748,758],{},[18,746,747],{},"Considère-la comme volée : révoque-la chez le service, génère-en une nouvelle.",[18,749,750,751,753,754,757],{},"Vérifie que ",[25,752,301],{}," est dans ",[25,755,756],{},".gitignore"," avant ton premier commit.",[18,759,760],{},"Utilise une clé différente par environnement (développement, production).",[10,762,764],{"id":763},"à-retenir","À retenir",[15,766,767,770,785,792,798],{},[18,768,769],{},"Tout ce qui s'exécute dans le navigateur est lisible : une clé d'API n'y a jamais sa place.",[18,771,772,773,775,776,778,779,781,782,28],{},"Déclare le secret dans ",[25,774,27],{}," hors de ",[25,777,221],{},", sa valeur dans ",[25,780,301],{}," sous le nom ",[25,783,784],{},"NUXT_...",[18,786,787,788,791],{},"Ta route ",[25,789,790],{},"server\u002Fapi\u002F"," appelle le service avec la clé ; la page appelle ta route.",[18,793,794,795,797],{},"Lis la config avec ",[25,796,454],{}," dans une route serveur.",[18,799,800],{},"Une clé qui a fuité se révoque et se remplace, on ne la « cache » pas après coup.",[802,803,804],"style",{},"html pre.shiki code .scfrF, html code.shiki .scfrF{--shiki-light:#1F2328;--shiki-default:#E6EDF3;--shiki-dark:#E6EDF3}html pre.shiki code .s5JFL, html code.shiki .s5JFL{--shiki-light:#116329;--shiki-default:#7EE787;--shiki-dark:#7EE787}html pre.shiki code .s5iOY, html code.shiki .s5iOY{--shiki-light:#0550AE;--shiki-default:#79C0FF;--shiki-dark:#79C0FF}html pre.shiki code .shWOn, html code.shiki .shWOn{--shiki-light:#0A3069;--shiki-default:#A5D6FF;--shiki-dark:#A5D6FF}html pre.shiki code .sd6ml, html code.shiki .sd6ml{--shiki-light:#6E7781;--shiki-default:#8B949E;--shiki-dark:#8B949E}html pre.shiki code .sOnC1, html code.shiki .sOnC1{--shiki-light:#CF222E;--shiki-default:#FF7B72;--shiki-dark:#FF7B72}html pre.shiki code .syCW7, html code.shiki .syCW7{--shiki-light:#8250DF;--shiki-default:#D2A8FF;--shiki-dark:#D2A8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sIXEE, html code.shiki .sIXEE{--shiki-light:#953800;--shiki-default:#FFA657;--shiki-dark:#FFA657}",{"title":58,"searchDepth":93,"depth":100,"links":806},[807,808,809,810,811,812,813,814,815],{"id":12,"depth":93,"text":13},{"id":34,"depth":93,"text":35},{"id":171,"depth":93,"text":172},{"id":210,"depth":93,"text":211},{"id":347,"depth":93,"text":348},{"id":462,"depth":93,"text":463},{"id":530,"depth":93,"text":531},{"id":741,"depth":93,"text":742},{"id":763,"depth":93,"text":764},"Pourquoi une clé d'API ne doit jamais atteindre le navigateur, et comment la garder côté serveur avec runtimeConfig et une route intermédiaire.","md","api",{},[43,27,301,821,822,47,823,824,305],"proxy","server\u002Fapi","Bearer","secret","intermediaire","\u002Fapi\u002Fcacher-une-cle-d-api",[828],"\u002Fapi\u002Fconsommer-une-api-externe",[830,834,837],{"nom":831,"url":832,"type":833,"anglais":399},"Configuration d'exécution, documentation officielle de Nuxt","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fguide\u002Fgoing-further\u002Fruntime-config","doc",{"nom":835,"url":836,"type":833,"anglais":399},"Le dossier server, documentation officielle de Nuxt","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fdirectory-structure\u002Fserver",{"nom":838,"url":839,"type":833},"Messages HTTP sur MDN","https:\u002F\u002Fdeveloper.mozilla.org\u002Ffr\u002Fdocs\u002FWeb\u002FHTTP\u002FGuides\u002FMessages",{"title":5,"description":816},[842,844,846],{"nom":843,"url":832},"Configuration d'exécution (en anglais)",{"nom":845,"url":836},"Le dossier server (en anglais)",{"nom":847,"url":848},"En-tête Authorization (MDN)","https:\u002F\u002Fdeveloper.mozilla.org\u002Ffr\u002Fdocs\u002FWeb\u002FHTTP\u002FReference\u002FHeaders\u002FAuthorization","api\u002F04.cacher-une-cle-d-api","yUQlloRr5a_QLGlYIH8reFK1Ga1aAaMlWv6xVBNb61k",[852,856,860,863,864,868,872],{"path":853,"title":854,"stem":855},"\u002Fapi\u002Fcomprendre-les-api","Comprendre les API","api\u002F01.comprendre-les-api",{"path":857,"title":858,"stem":859},"\u002Fapi\u002Fmethodes-et-codes-de-statut","Méthodes et codes de statut","api\u002F02.methodes-et-codes-de-statut",{"path":828,"title":861,"stem":862},"Consommer une API externe","api\u002F03.consommer-une-api-externe",{"path":826,"title":5,"stem":849},{"path":865,"title":866,"stem":867},"\u002Fapi\u002Fecrire-une-api-complete","Écrire une API complète","api\u002F05.ecrire-une-api-complete",{"path":869,"title":870,"stem":871},"\u002Fapi\u002Fsecurite-et-limites","Sécurité et limites","api\u002F06.securite-et-limites",{"path":873,"title":874,"stem":875},"\u002Fapi","API","api\u002Findex",[877,879],{"title":861,"path":828,"stem":862,"description":878,"children":-1},"Appeler une API publique depuis Nuxt, transformer sa réponse pour ne garder que l'utile et gérer les erreurs et les paramètres.",{"title":866,"path":865,"stem":867,"description":880,"children":-1},"Construire les cinq routes d'une API de recettes (lire, créer, modifier, supprimer) avec validation des entrées et erreurs claires.",[882],{"path":828,"title":861,"ligne":818},[884,886,887,888,889,890,892],{"path":853,"title":854,"niveau":885,"stem":855},"debutant",{"path":857,"title":858,"niveau":885,"stem":859},{"path":828,"title":861,"niveau":885,"stem":862},{"path":826,"title":5,"niveau":825,"stem":849},{"path":865,"title":866,"niveau":825,"stem":867},{"path":869,"title":870,"niveau":891,"stem":871},"avance",{"path":873,"title":874,"niveau":893,"stem":875},"memo",1794583450094]