[{"data":1,"prerenderedAt":1354},["ShallowReactive",2],{"page-\u002Fapi\u002Fecrire-une-api-complete":3,"pieces-api":1311,"autour-\u002Fapi\u002Fecrire-une-api-complete":1336,"prerequis-\u002Fapi\u002Fecrire-une-api-complete":1341,"plan-api":1343},{"id":4,"title":5,"body":6,"description":1271,"extension":1272,"ligne":1273,"meta":1274,"motsCles":1275,"navigation":221,"niveau":1284,"path":1285,"prerequis":1286,"ressources":1288,"seo":1299,"sources":1300,"stem":1309,"__hash__":1310},"docs\u002Fapi\u002F05.ecrire-une-api-complete.md","Écrire une API complète",{"type":7,"value":8,"toc":1261},"minimark",[9,14,27,31,41,139,143,150,421,427,431,443,583,593,596,742,756,760,877,894,898,901,1043,1053,1146,1160,1164,1219,1226,1230,1257],[10,11,13],"h2",{"id":12},"ce-que-tu-vas-apprendre","Ce que tu vas apprendre",[15,16,17,21,24],"ul",{},[18,19,20],"li",{},"Organiser les routes d'un CRUD (créer, lire, modifier, supprimer).",[18,22,23],{},"Valider les données reçues avant de les utiliser.",[18,25,26],{},"Renvoyer le bon code HTTP dans chaque cas.",[10,28,30],{"id":29},"le-plan-des-routes","Le plan des routes",[32,33,34,35,40],"p",{},"Le cours ",[36,37,39],"a",{"href":38},"\u002Fnuxt\u002Froutes-api","Les routes API"," a montré une route. Une API complète en a cinq pour les recettes, une par fichier :",[42,43,44,60],"table",{},[45,46,47],"thead",{},[48,49,50,54,57],"tr",{},[51,52,53],"th",{},"Fichier",[51,55,56],{},"Requête",[51,58,59],{},"Réponse",[61,62,63,80,95,110,124],"tbody",{},[48,64,65,72,77],{},[66,67,68],"td",{},[69,70,71],"code",{},"server\u002Fapi\u002Frecettes\u002Findex.get.ts",[66,73,74],{},[69,75,76],{},"GET \u002Fapi\u002Frecettes",[66,78,79],{},"200, la liste",[48,81,82,87,92],{},[66,83,84],{},[69,85,86],{},"server\u002Fapi\u002Frecettes\u002Findex.post.ts",[66,88,89],{},[69,90,91],{},"POST \u002Fapi\u002Frecettes",[66,93,94],{},"201, la recette créée",[48,96,97,102,107],{},[66,98,99],{},[69,100,101],{},"server\u002Fapi\u002Frecettes\u002F[id].get.ts",[66,103,104],{},[69,105,106],{},"GET \u002Fapi\u002Frecettes\u002F2",[66,108,109],{},"200 ou 404",[48,111,112,117,122],{},[66,113,114],{},[69,115,116],{},"server\u002Fapi\u002Frecettes\u002F[id].patch.ts",[66,118,119],{},[69,120,121],{},"PATCH \u002Fapi\u002Frecettes\u002F2",[66,123,109],{},[48,125,126,131,136],{},[66,127,128],{},[69,129,130],{},"server\u002Fapi\u002Frecettes\u002F[id].delete.ts",[66,132,133],{},[69,134,135],{},"DELETE \u002Fapi\u002Frecettes\u002F2",[66,137,138],{},"204 ou 404",[10,140,142],{"id":141},"un-stockage-partagé","Un stockage partagé",[32,144,145,146,149],{},"Pour l'exemple, les recettes vivent dans un tableau en mémoire. Un fichier de ",[69,147,148],{},"server\u002Futils\u002F"," est importé automatiquement dans les routes :",[151,152,158],"pre",{"className":153,"code":154,"filename":155,"language":156,"meta":157,"style":157},"language-ts shiki shiki-themes github-light-default github-dark-default github-dark-default","export type Recette = { id: number, titre: string, duree: number }\n\nexport const recettes: Recette[] = [\n  { id: 1, titre: 'Crêpes', duree: 20 },\n  { id: 2, titre: 'Ratatouille', duree: 60 }\n]\n\nexport function trouverRecette(id: number) {\n  const recette = recettes.find(r => r.id === id)\n  if (!recette) {\n    throw createError({ status: 404, statusText: 'Recette introuvable' })\n  }\n  return recette\n}\n","server\u002Futils\u002Frecettes.ts","ts","",[69,159,160,216,223,247,272,292,298,303,327,361,376,400,406,415],{"__ignoreMap":157},[161,162,165,169,172,176,179,183,186,189,193,196,199,201,204,206,209,211,213],"span",{"class":163,"line":164},"line",1,[161,166,168],{"class":167},"sOnC1","export",[161,170,171],{"class":167}," type",[161,173,175],{"class":174},"sIXEE"," Recette",[161,177,178],{"class":167}," =",[161,180,182],{"class":181},"scfrF"," { ",[161,184,185],{"class":174},"id",[161,187,188],{"class":167},":",[161,190,192],{"class":191},"s5iOY"," number",[161,194,195],{"class":181},", ",[161,197,198],{"class":174},"titre",[161,200,188],{"class":167},[161,202,203],{"class":191}," string",[161,205,195],{"class":181},[161,207,208],{"class":174},"duree",[161,210,188],{"class":167},[161,212,192],{"class":191},[161,214,215],{"class":181}," }\n",[161,217,219],{"class":163,"line":218},2,[161,220,222],{"emptyLinePlaceholder":221},true,"\n",[161,224,226,228,231,234,236,238,241,244],{"class":163,"line":225},3,[161,227,168],{"class":167},[161,229,230],{"class":167}," const",[161,232,233],{"class":191}," recettes",[161,235,188],{"class":167},[161,237,175],{"class":174},[161,239,240],{"class":181},"[] ",[161,242,243],{"class":167},"=",[161,245,246],{"class":181}," [\n",[161,248,250,253,256,259,263,266,269],{"class":163,"line":249},4,[161,251,252],{"class":181},"  { id: ",[161,254,255],{"class":191},"1",[161,257,258],{"class":181},", titre: ",[161,260,262],{"class":261},"shWOn","'Crêpes'",[161,264,265],{"class":181},", duree: ",[161,267,268],{"class":191},"20",[161,270,271],{"class":181}," },\n",[161,273,275,277,280,282,285,287,290],{"class":163,"line":274},5,[161,276,252],{"class":181},[161,278,279],{"class":191},"2",[161,281,258],{"class":181},[161,283,284],{"class":261},"'Ratatouille'",[161,286,265],{"class":181},[161,288,289],{"class":191},"60",[161,291,215],{"class":181},[161,293,295],{"class":163,"line":294},6,[161,296,297],{"class":181},"]\n",[161,299,301],{"class":163,"line":300},7,[161,302,222],{"emptyLinePlaceholder":221},[161,304,306,308,311,315,318,320,322,324],{"class":163,"line":305},8,[161,307,168],{"class":167},[161,309,310],{"class":167}," function",[161,312,314],{"class":313},"syCW7"," trouverRecette",[161,316,317],{"class":181},"(",[161,319,185],{"class":174},[161,321,188],{"class":167},[161,323,192],{"class":191},[161,325,326],{"class":181},") {\n",[161,328,330,333,336,338,341,344,346,349,352,355,358],{"class":163,"line":329},9,[161,331,332],{"class":167},"  const",[161,334,335],{"class":191}," recette",[161,337,178],{"class":167},[161,339,340],{"class":181}," recettes.",[161,342,343],{"class":313},"find",[161,345,317],{"class":181},[161,347,348],{"class":174},"r",[161,350,351],{"class":167}," =>",[161,353,354],{"class":181}," r.id ",[161,356,357],{"class":167},"===",[161,359,360],{"class":181}," id)\n",[161,362,364,367,370,373],{"class":163,"line":363},10,[161,365,366],{"class":167},"  if",[161,368,369],{"class":181}," (",[161,371,372],{"class":167},"!",[161,374,375],{"class":181},"recette) {\n",[161,377,379,382,385,388,391,394,397],{"class":163,"line":378},11,[161,380,381],{"class":167},"    throw",[161,383,384],{"class":313}," createError",[161,386,387],{"class":181},"({ status: ",[161,389,390],{"class":191},"404",[161,392,393],{"class":181},", statusText: ",[161,395,396],{"class":261},"'Recette introuvable'",[161,398,399],{"class":181}," })\n",[161,401,403],{"class":163,"line":402},12,[161,404,405],{"class":181},"  }\n",[161,407,409,412],{"class":163,"line":408},13,[161,410,411],{"class":167},"  return",[161,413,414],{"class":181}," recette\n",[161,416,418],{"class":163,"line":417},14,[161,419,420],{"class":181},"}\n",[422,423,424],"warning",{},[32,425,426],{},"Un tableau en mémoire est vidé à chaque redémarrage du serveur. C'est suffisant pour apprendre ; en vrai, tu utilises une base de données.",[10,428,430],{"id":429},"valider-les-entrées","Valider les entrées",[32,432,433,434,438,439,442],{},"Tout ce que le client envoie est à vérifier. Une bibliothèque de validation comme ",[435,436,437],"strong",{},"Zod"," (à installer avec ",[69,440,441],{},"npm install zod",") décrit la forme attendue une fois pour toutes :",[151,444,447],{"className":153,"code":445,"filename":446,"language":156,"meta":157,"style":157},"import { z } from 'zod'\n\nexport const schemaRecette = z.object({\n  titre: z.string().trim().min(1).max(100),\n  duree: z.number().int().positive().max(1440)\n})\n\nexport const schemaModification = schemaRecette.partial()\n","server\u002Futils\u002Fschemas.ts",[69,448,449,463,467,487,524,554,559,563],{"__ignoreMap":157},[161,450,451,454,457,460],{"class":163,"line":164},[161,452,453],{"class":167},"import",[161,455,456],{"class":181}," { z } ",[161,458,459],{"class":167},"from",[161,461,462],{"class":261}," 'zod'\n",[161,464,465],{"class":163,"line":218},[161,466,222],{"emptyLinePlaceholder":221},[161,468,469,471,473,476,478,481,484],{"class":163,"line":225},[161,470,168],{"class":167},[161,472,230],{"class":167},[161,474,475],{"class":191}," schemaRecette",[161,477,178],{"class":167},[161,479,480],{"class":181}," z.",[161,482,483],{"class":313},"object",[161,485,486],{"class":181},"({\n",[161,488,489,492,495,498,501,503,506,508,510,513,516,518,521],{"class":163,"line":249},[161,490,491],{"class":181},"  titre: z.",[161,493,494],{"class":313},"string",[161,496,497],{"class":181},"().",[161,499,500],{"class":313},"trim",[161,502,497],{"class":181},[161,504,505],{"class":313},"min",[161,507,317],{"class":181},[161,509,255],{"class":191},[161,511,512],{"class":181},").",[161,514,515],{"class":313},"max",[161,517,317],{"class":181},[161,519,520],{"class":191},"100",[161,522,523],{"class":181},"),\n",[161,525,526,529,532,534,537,539,542,544,546,548,551],{"class":163,"line":274},[161,527,528],{"class":181},"  duree: z.",[161,530,531],{"class":313},"number",[161,533,497],{"class":181},[161,535,536],{"class":313},"int",[161,538,497],{"class":181},[161,540,541],{"class":313},"positive",[161,543,497],{"class":181},[161,545,515],{"class":313},[161,547,317],{"class":181},[161,549,550],{"class":191},"1440",[161,552,553],{"class":181},")\n",[161,555,556],{"class":163,"line":294},[161,557,558],{"class":181},"})\n",[161,560,561],{"class":163,"line":300},[161,562,222],{"emptyLinePlaceholder":221},[161,564,565,567,569,572,574,577,580],{"class":163,"line":305},[161,566,168],{"class":167},[161,568,230],{"class":167},[161,570,571],{"class":191}," schemaModification",[161,573,178],{"class":167},[161,575,576],{"class":181}," schemaRecette.",[161,578,579],{"class":313},"partial",[161,581,582],{"class":181},"()\n",[32,584,585,588,589,592],{},[69,586,587],{},"partial()"," rend chaque champ facultatif : c'est ce qu'il faut pour un ",[69,590,591],{},"PATCH",", qui modifie en partie.",[32,594,595],{},"Ensuite, une petite fonction applique le schéma et renvoie un 400 lisible :",[151,597,599],{"className":153,"code":598,"filename":446,"language":156,"meta":157,"style":157},"export function verifier\u003CT>(schema: z.ZodType\u003CT>, donnees: unknown): T {\n  const resultat = schema.safeParse(donnees)\n  if (!resultat.success) {\n    throw createError({\n      status: 400,\n      statusText: 'Donnees invalides',\n      data: resultat.error.issues\n    })\n  }\n  return resultat.data\n}\n",[69,600,601,659,677,688,696,707,717,722,727,731,738],{"__ignoreMap":157},[161,602,603,605,607,610,613,616,619,622,624,627,630,633,635,637,640,643,645,648,651,653,656],{"class":163,"line":164},[161,604,168],{"class":167},[161,606,310],{"class":167},[161,608,609],{"class":313}," verifier",[161,611,612],{"class":181},"\u003C",[161,614,615],{"class":174},"T",[161,617,618],{"class":181},">(",[161,620,621],{"class":174},"schema",[161,623,188],{"class":167},[161,625,626],{"class":174}," z",[161,628,629],{"class":181},".",[161,631,632],{"class":174},"ZodType",[161,634,612],{"class":181},[161,636,615],{"class":174},[161,638,639],{"class":181},">, ",[161,641,642],{"class":174},"donnees",[161,644,188],{"class":167},[161,646,647],{"class":191}," unknown",[161,649,650],{"class":181},")",[161,652,188],{"class":167},[161,654,655],{"class":174}," T",[161,657,658],{"class":181}," {\n",[161,660,661,663,666,668,671,674],{"class":163,"line":218},[161,662,332],{"class":167},[161,664,665],{"class":191}," resultat",[161,667,178],{"class":167},[161,669,670],{"class":181}," schema.",[161,672,673],{"class":313},"safeParse",[161,675,676],{"class":181},"(donnees)\n",[161,678,679,681,683,685],{"class":163,"line":225},[161,680,366],{"class":167},[161,682,369],{"class":181},[161,684,372],{"class":167},[161,686,687],{"class":181},"resultat.success) {\n",[161,689,690,692,694],{"class":163,"line":249},[161,691,381],{"class":167},[161,693,384],{"class":313},[161,695,486],{"class":181},[161,697,698,701,704],{"class":163,"line":274},[161,699,700],{"class":181},"      status: ",[161,702,703],{"class":191},"400",[161,705,706],{"class":181},",\n",[161,708,709,712,715],{"class":163,"line":294},[161,710,711],{"class":181},"      statusText: ",[161,713,714],{"class":261},"'Donnees invalides'",[161,716,706],{"class":181},[161,718,719],{"class":163,"line":300},[161,720,721],{"class":181},"      data: resultat.error.issues\n",[161,723,724],{"class":163,"line":305},[161,725,726],{"class":181},"    })\n",[161,728,729],{"class":163,"line":329},[161,730,405],{"class":181},[161,732,733,735],{"class":163,"line":363},[161,734,411],{"class":167},[161,736,737],{"class":181}," resultat.data\n",[161,739,740],{"class":163,"line":378},[161,741,420],{"class":181},[743,744,745],"tip",{},[32,746,747,748,751,752,755],{},"h3 propose aussi ",[69,749,750],{},"readValidatedBody",", qui lit et valide le corps en une étape avec un validateur comme Zod. Écrire ",[69,753,754],{},"verifier"," toi-même te laisse choisir le code et le message d'erreur.",[10,757,759],{"id":758},"créer-post","Créer : POST",[151,761,763],{"className":153,"code":762,"filename":86,"language":156,"meta":157,"style":157},"export default defineEventHandler(async (event) => {\n  const corps = verifier(schemaRecette, await readBody(event))\n  const nouvelle = { id: Date.now(), ...corps }\n  recettes.push(nouvelle)\n\n  setResponseStatus(event, 201)\n  return nouvelle\n})\n",[69,764,765,788,811,835,846,850,863,870],{"__ignoreMap":157},[161,766,767,769,772,775,777,780,783,786],{"class":163,"line":164},[161,768,168],{"class":167},[161,770,771],{"class":167}," default",[161,773,774],{"class":313}," defineEventHandler",[161,776,317],{"class":174},[161,778,779],{"class":167},"async",[161,781,782],{"class":174}," (event) ",[161,784,785],{"class":167},"=>",[161,787,658],{"class":181},[161,789,790,792,795,797,799,802,805,808],{"class":163,"line":218},[161,791,332],{"class":167},[161,793,794],{"class":191}," corps",[161,796,178],{"class":167},[161,798,609],{"class":313},[161,800,801],{"class":181},"(schemaRecette, ",[161,803,804],{"class":167},"await",[161,806,807],{"class":313}," readBody",[161,809,810],{"class":181},"(event))\n",[161,812,813,815,818,820,823,826,829,832],{"class":163,"line":225},[161,814,332],{"class":167},[161,816,817],{"class":191}," nouvelle",[161,819,178],{"class":167},[161,821,822],{"class":181}," { id: Date.",[161,824,825],{"class":313},"now",[161,827,828],{"class":181},"(), ",[161,830,831],{"class":167},"...",[161,833,834],{"class":181},"corps }\n",[161,836,837,840,843],{"class":163,"line":249},[161,838,839],{"class":181},"  recettes.",[161,841,842],{"class":313},"push",[161,844,845],{"class":181},"(nouvelle)\n",[161,847,848],{"class":163,"line":274},[161,849,222],{"emptyLinePlaceholder":221},[161,851,852,855,858,861],{"class":163,"line":294},[161,853,854],{"class":313},"  setResponseStatus",[161,856,857],{"class":181},"(event, ",[161,859,860],{"class":191},"201",[161,862,553],{"class":181},[161,864,865,867],{"class":163,"line":300},[161,866,411],{"class":167},[161,868,869],{"class":181}," nouvelle\n",[161,871,872,875],{"class":163,"line":305},[161,873,874],{"class":181},"}",[161,876,553],{"class":174},[32,878,879,880,882,883,885,886,889,890,893],{},"Seuls ",[69,881,198],{}," et ",[69,884,208],{}," sont repris : si le client envoie ",[69,887,888],{},"id: 1"," ou un champ inventé, il est ignoré. ",[69,891,892],{},"setResponseStatus(event, 201)"," annonce la création.",[10,895,897],{"id":896},"lire-modifier-supprimer","Lire, modifier, supprimer",[32,899,900],{},"Les trois routes d'une recette précise partagent le même début : lire l'identifiant dans l'adresse et le valider.",[151,902,904],{"className":153,"code":903,"filename":116,"language":156,"meta":157,"style":157},"export default defineEventHandler(async (event) => {\n  const id = Number(getRouterParam(event, 'id'))\n  if (!Number.isInteger(id)) {\n    throw createError({ status: 400, statusText: 'Identifiant invalide' })\n  }\n\n  const recette = trouverRecette(id)\n  const modifs = verifier(schemaModification, await readBody(event))\n  return Object.assign(recette, modifs)\n})\n",[69,905,906,924,949,966,983,987,991,1004,1024,1037],{"__ignoreMap":157},[161,907,908,910,912,914,916,918,920,922],{"class":163,"line":164},[161,909,168],{"class":167},[161,911,771],{"class":167},[161,913,774],{"class":313},[161,915,317],{"class":174},[161,917,779],{"class":167},[161,919,782],{"class":174},[161,921,785],{"class":167},[161,923,658],{"class":181},[161,925,926,928,931,933,936,938,941,943,946],{"class":163,"line":218},[161,927,332],{"class":167},[161,929,930],{"class":191}," id",[161,932,178],{"class":167},[161,934,935],{"class":313}," Number",[161,937,317],{"class":181},[161,939,940],{"class":313},"getRouterParam",[161,942,857],{"class":181},[161,944,945],{"class":261},"'id'",[161,947,948],{"class":181},"))\n",[161,950,951,953,955,957,960,963],{"class":163,"line":225},[161,952,366],{"class":167},[161,954,369],{"class":181},[161,956,372],{"class":167},[161,958,959],{"class":181},"Number.",[161,961,962],{"class":313},"isInteger",[161,964,965],{"class":181},"(id)) {\n",[161,967,968,970,972,974,976,978,981],{"class":163,"line":249},[161,969,381],{"class":167},[161,971,384],{"class":313},[161,973,387],{"class":181},[161,975,703],{"class":191},[161,977,393],{"class":181},[161,979,980],{"class":261},"'Identifiant invalide'",[161,982,399],{"class":181},[161,984,985],{"class":163,"line":274},[161,986,405],{"class":181},[161,988,989],{"class":163,"line":294},[161,990,222],{"emptyLinePlaceholder":221},[161,992,993,995,997,999,1001],{"class":163,"line":300},[161,994,332],{"class":167},[161,996,335],{"class":191},[161,998,178],{"class":167},[161,1000,314],{"class":313},[161,1002,1003],{"class":181},"(id)\n",[161,1005,1006,1008,1011,1013,1015,1018,1020,1022],{"class":163,"line":305},[161,1007,332],{"class":167},[161,1009,1010],{"class":191}," modifs",[161,1012,178],{"class":167},[161,1014,609],{"class":313},[161,1016,1017],{"class":181},"(schemaModification, ",[161,1019,804],{"class":167},[161,1021,807],{"class":313},[161,1023,810],{"class":181},[161,1025,1026,1028,1031,1034],{"class":163,"line":329},[161,1027,411],{"class":167},[161,1029,1030],{"class":181}," Object.",[161,1032,1033],{"class":313},"assign",[161,1035,1036],{"class":181},"(recette, modifs)\n",[161,1038,1039,1041],{"class":163,"line":363},[161,1040,874],{"class":181},[161,1042,553],{"class":174},[32,1044,1045,1048,1049,1052],{},[69,1046,1047],{},"[id].get.ts"," fait pareil sans lire le corps, et renvoie ",[69,1050,1051],{},"trouverRecette(id)",". Pour la suppression :",[151,1054,1056],{"className":153,"code":1055,"filename":130,"language":156,"meta":157,"style":157},"export default defineEventHandler((event) => {\n  const id = Number(getRouterParam(event, 'id'))\n  const recette = trouverRecette(id)\n  recettes.splice(recettes.indexOf(recette), 1)\n\n  setResponseStatus(event, 204)\n})\n",[69,1057,1058,1073,1093,1105,1125,1129,1140],{"__ignoreMap":157},[161,1059,1060,1062,1064,1066,1069,1071],{"class":163,"line":164},[161,1061,168],{"class":167},[161,1063,771],{"class":167},[161,1065,774],{"class":313},[161,1067,1068],{"class":174},"((event) ",[161,1070,785],{"class":167},[161,1072,658],{"class":181},[161,1074,1075,1077,1079,1081,1083,1085,1087,1089,1091],{"class":163,"line":218},[161,1076,332],{"class":167},[161,1078,930],{"class":191},[161,1080,178],{"class":167},[161,1082,935],{"class":313},[161,1084,317],{"class":181},[161,1086,940],{"class":313},[161,1088,857],{"class":181},[161,1090,945],{"class":261},[161,1092,948],{"class":181},[161,1094,1095,1097,1099,1101,1103],{"class":163,"line":225},[161,1096,332],{"class":167},[161,1098,335],{"class":191},[161,1100,178],{"class":167},[161,1102,314],{"class":313},[161,1104,1003],{"class":181},[161,1106,1107,1109,1112,1115,1118,1121,1123],{"class":163,"line":249},[161,1108,839],{"class":181},[161,1110,1111],{"class":313},"splice",[161,1113,1114],{"class":181},"(recettes.",[161,1116,1117],{"class":313},"indexOf",[161,1119,1120],{"class":181},"(recette), ",[161,1122,255],{"class":191},[161,1124,553],{"class":181},[161,1126,1127],{"class":163,"line":274},[161,1128,222],{"emptyLinePlaceholder":221},[161,1130,1131,1133,1135,1138],{"class":163,"line":294},[161,1132,854],{"class":313},[161,1134,857],{"class":181},[161,1136,1137],{"class":191},"204",[161,1139,553],{"class":181},[161,1141,1142,1144],{"class":163,"line":300},[161,1143,874],{"class":181},[161,1145,553],{"class":174},[32,1147,1148,1149,1151,1152,1155,1156,1159],{},"Un ",[69,1150,185],{}," non numérique donne ",[69,1153,1154],{},"NaN",", que ",[69,1157,1158],{},"trouverRecette"," ne trouve pas : tu obtiens un 404, ce qui reste cohérent.",[10,1161,1163],{"id":1162},"lappeler-depuis-une-page","L'appeler depuis une page",[151,1165,1167],{"className":153,"code":1166,"language":156,"meta":157,"style":157},"await $fetch('\u002Fapi\u002Frecettes', { method: 'POST', body: { titre: 'Gaufres', duree: 30 } })\nawait $fetch('\u002Fapi\u002Frecettes\u002F2', { method: 'DELETE' })\n",[69,1168,1169,1201],{"__ignoreMap":157},[161,1170,1171,1173,1176,1178,1181,1184,1187,1190,1193,1195,1198],{"class":163,"line":164},[161,1172,804],{"class":167},[161,1174,1175],{"class":313}," $fetch",[161,1177,317],{"class":181},[161,1179,1180],{"class":261},"'\u002Fapi\u002Frecettes'",[161,1182,1183],{"class":181},", { method: ",[161,1185,1186],{"class":261},"'POST'",[161,1188,1189],{"class":181},", body: { titre: ",[161,1191,1192],{"class":261},"'Gaufres'",[161,1194,265],{"class":181},[161,1196,1197],{"class":191},"30",[161,1199,1200],{"class":181}," } })\n",[161,1202,1203,1205,1207,1209,1212,1214,1217],{"class":163,"line":218},[161,1204,804],{"class":167},[161,1206,1175],{"class":313},[161,1208,317],{"class":181},[161,1210,1211],{"class":261},"'\u002Fapi\u002Frecettes\u002F2'",[161,1213,1183],{"class":181},[161,1215,1216],{"class":261},"'DELETE'",[161,1218,399],{"class":181},[32,1220,1221,1222,1225],{},"Teste aussi les cas d'erreur avec ",[69,1223,1224],{},"curl"," : un corps vide doit renvoyer 400, un identifiant inconnu 404.",[10,1227,1229],{"id":1228},"à-retenir","À retenir",[15,1231,1232,1239,1242,1245,1251],{},[18,1233,1234,1235,1238],{},"Un CRUD, ce sont cinq routes : un fichier par méthode, avec ",[69,1236,1237],{},"[id]"," pour une recette précise.",[18,1240,1241],{},"Valide tout ce qui arrive (corps, paramètres) et ne reprends que les champs attendus.",[18,1243,1244],{},"Choisis le bon code : 201 à la création, 204 à la suppression, 400 pour une entrée invalide, 404 pour l'absent.",[18,1246,1247,1250],{},[69,1248,1249],{},"createError({ status, statusText })"," interrompt la route avec une erreur HTTP.",[18,1252,1253,1254,1256],{},"Range le code partagé dans ",[69,1255,148],{}," : il est importé automatiquement.",[1258,1259,1260],"style",{},"html pre.shiki code .sOnC1, html code.shiki .sOnC1{--shiki-light:#CF222E;--shiki-default:#FF7B72;--shiki-dark:#FF7B72}html pre.shiki code .sIXEE, html code.shiki .sIXEE{--shiki-light:#953800;--shiki-default:#FFA657;--shiki-dark:#FFA657}html pre.shiki code .scfrF, html code.shiki .scfrF{--shiki-light:#1F2328;--shiki-default:#E6EDF3;--shiki-dark:#E6EDF3}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 .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);}",{"title":157,"searchDepth":218,"depth":225,"links":1262},[1263,1264,1265,1266,1267,1268,1269,1270],{"id":12,"depth":218,"text":13},{"id":29,"depth":218,"text":30},{"id":141,"depth":218,"text":142},{"id":429,"depth":218,"text":430},{"id":758,"depth":218,"text":759},{"id":896,"depth":218,"text":897},{"id":1162,"depth":218,"text":1163},{"id":1228,"depth":218,"text":1229},"Construire les cinq routes d'une API de recettes (lire, créer, modifier, supprimer) avec validation des entrées et erreurs claires.","md","api",{},[1276,1277,1278,1279,1280,1281,1282,1283,591],"CRUD","validation","zod","createError","setResponseStatus","readBody","server\u002Futils","server\u002Fapi","intermediaire","\u002Fapi\u002Fecrire-une-api-complete",[1287],"\u002Fapi\u002Fcacher-une-cle-d-api",[1289,1293,1296],{"nom":1290,"url":1291,"type":1292,"anglais":221},"Le dossier server, documentation officielle de Nuxt","https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fdirectory-structure\u002Fserver","doc",{"nom":1294,"url":1295,"type":1292},"Méthodes de requête HTTP sur MDN","https:\u002F\u002Fdeveloper.mozilla.org\u002Ffr\u002Fdocs\u002FWeb\u002FHTTP\u002FReference\u002FMethods",{"nom":1297,"url":1298,"type":1292},"Codes de statut de réponse HTTP sur MDN","https:\u002F\u002Fdeveloper.mozilla.org\u002Ffr\u002Fdocs\u002FWeb\u002FHTTP\u002FReference\u002FStatus",{"title":5,"description":1271},[1301,1303,1306],{"nom":1302,"url":1291},"Le dossier server (en anglais)",{"nom":1304,"url":1305},"Utilitaires de requête h3 (en anglais)","https:\u002F\u002Fh3.dev\u002Futils\u002Frequest",{"nom":1307,"url":1308},"Erreurs h3 (en anglais)","https:\u002F\u002Fh3.dev\u002Fguide\u002Fbasics\u002Ferror","api\u002F05.ecrire-une-api-complete","LFDboR981BmM441qrQnSkajzxcQnHFvMfLdn4a9uSyM",[1312,1316,1320,1324,1327,1328,1332],{"path":1313,"title":1314,"stem":1315},"\u002Fapi\u002Fcomprendre-les-api","Comprendre les API","api\u002F01.comprendre-les-api",{"path":1317,"title":1318,"stem":1319},"\u002Fapi\u002Fmethodes-et-codes-de-statut","Méthodes et codes de statut","api\u002F02.methodes-et-codes-de-statut",{"path":1321,"title":1322,"stem":1323},"\u002Fapi\u002Fconsommer-une-api-externe","Consommer une API externe","api\u002F03.consommer-une-api-externe",{"path":1287,"title":1325,"stem":1326},"Cacher une clé d'API","api\u002F04.cacher-une-cle-d-api",{"path":1285,"title":5,"stem":1309},{"path":1329,"title":1330,"stem":1331},"\u002Fapi\u002Fsecurite-et-limites","Sécurité et limites","api\u002F06.securite-et-limites",{"path":1333,"title":1334,"stem":1335},"\u002Fapi","API","api\u002Findex",[1337,1339],{"title":1325,"path":1287,"stem":1326,"description":1338,"children":-1},"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.",{"title":1330,"path":1329,"stem":1331,"description":1340,"children":-1},"Protéger une API : ne jamais faire confiance aux entrées, contrôler l'accès, régler CORS, mettre en cache et limiter les abus.",[1342],{"path":1287,"title":1325,"ligne":1273},[1344,1346,1347,1348,1349,1350,1352],{"path":1313,"title":1314,"niveau":1345,"stem":1315},"debutant",{"path":1317,"title":1318,"niveau":1345,"stem":1319},{"path":1321,"title":1322,"niveau":1345,"stem":1323},{"path":1287,"title":1325,"niveau":1284,"stem":1326},{"path":1285,"title":5,"niveau":1284,"stem":1309},{"path":1329,"title":1330,"niveau":1351,"stem":1331},"avance",{"path":1333,"title":1334,"niveau":1353,"stem":1335},"memo",1794583450250]