[{"data":1,"prerenderedAt":1072},["ShallowReactive",2],{"article-\u002Fblog\u002Fapp-fullstack-nuxt-nitro-prisma":3},{"id":4,"title":5,"body":6,"brouillon":1058,"date":1059,"description":1060,"extension":1061,"image":1062,"imageAlt":1062,"meta":1063,"navigation":304,"path":1064,"seo":1065,"stem":1066,"tags":1067,"__hash__":1071},"blog\u002Fblog\u002Fapp-fullstack-nuxt-nitro-prisma.md","Une app fullstack avec Nuxt : server routes Nitro et Prisma",{"type":7,"value":8,"toc":1048},"minimark",[9,13,22,36,65,76,180,209,213,216,263,450,453,476,480,487,536,543,547,623,626,759,769,773,780,1018,1027,1031,1034,1038,1044],[10,11,12],"p",{},"L'un des atouts les plus sous-estimés de Nuxt, c'est qu'il n'a pas besoin\nd'un backend séparé pour être fullstack. Le serveur est déjà là, sous le nom\nde Nitro. Ajoutez Prisma pour parler à une base de données, et vous tenez\nune application complète dans un seul dépôt, en TypeScript du front jusqu'à\nla requête SQL. Voici comment les pièces s'emboîtent.",[14,15,17,18],"h2",{"id":16},"le-dossier-server","Le dossier ",[19,20,21],"code",{},"server\u002F",[10,23,24,25,27,28,31,32,35],{},"Tout le code serveur vit dans ",[19,26,21],{},", à la racine du projet (hors de\n",[19,29,30],{},"app\u002F",", car il ne tourne jamais dans le navigateur). La convention la plus\nutile est ",[19,33,34],{},"server\u002Fapi\u002F"," : chaque fichier y devient un point d'accès HTTP.",[37,38,39,49,57],"ul",{},[40,41,42,45,46],"li",{},[19,43,44],{},"server\u002Fapi\u002Farticles.get.ts"," répond à ",[19,47,48],{},"GET \u002Fapi\u002Farticles",[40,50,51,45,54],{},[19,52,53],{},"server\u002Fapi\u002Farticles.post.ts",[19,55,56],{},"POST \u002Fapi\u002Farticles",[40,58,59,45,62],{},[19,60,61],{},"server\u002Fapi\u002Farticles\u002F[id].get.ts",[19,63,64],{},"GET \u002Fapi\u002Farticles\u002F42",[10,66,67,68,71,72,75],{},"Le suffixe (",[19,69,70],{},".get",", ",[19,73,74],{},".post",") fixe la méthode HTTP, et l'arborescence fixe\nl'URL. Comme pour les pages : le disque décrit l'API.",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-typescript shiki shiki-themes github-dark","\u002F\u002F server\u002Fapi\u002Fhello.get.ts\nexport default defineEventHandler((event) => {\n  const { nom } = getQuery(event)\n  return { message: `Bonjour ${nom ?? 'le monde'}` }\n})\n","typescript","",[19,84,85,94,125,150,174],{"__ignoreMap":82},[86,87,90],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sAwPA","\u002F\u002F server\u002Fapi\u002Fhello.get.ts\n",[86,95,97,101,104,108,112,116,119,122],{"class":88,"line":96},2,[86,98,100],{"class":99},"snl16","export",[86,102,103],{"class":99}," default",[86,105,107],{"class":106},"svObZ"," defineEventHandler",[86,109,111],{"class":110},"s95oV","((",[86,113,115],{"class":114},"s9osk","event",[86,117,118],{"class":110},") ",[86,120,121],{"class":99},"=>",[86,123,124],{"class":110}," {\n",[86,126,128,131,134,138,141,144,147],{"class":88,"line":127},3,[86,129,130],{"class":99},"  const",[86,132,133],{"class":110}," { ",[86,135,137],{"class":136},"sDLfK","nom",[86,139,140],{"class":110}," } ",[86,142,143],{"class":99},"=",[86,145,146],{"class":106}," getQuery",[86,148,149],{"class":110},"(event)\n",[86,151,153,156,159,163,165,168,171],{"class":88,"line":152},4,[86,154,155],{"class":99},"  return",[86,157,158],{"class":110}," { message: ",[86,160,162],{"class":161},"sU2Wk","`Bonjour ${",[86,164,137],{"class":110},[86,166,167],{"class":99}," ??",[86,169,170],{"class":161}," 'le monde'}`",[86,172,173],{"class":110}," }\n",[86,175,177],{"class":88,"line":176},5,[86,178,179],{"class":110},"})\n",[10,181,182,185,186,188,189,193,194,197,198,71,201,204,205,208],{},[19,183,184],{},"defineEventHandler"," reçoit un ",[19,187,115],{}," (la requête), et ",[190,191,192],"strong",{},"ce que vous\nretournez devient la réponse JSON",". Pas de ",[19,195,196],{},"res.json()",", pas de\nsérialisation manuelle. Les utilitaires comme ",[19,199,200],{},"getQuery",[19,202,203],{},"readBody"," ou\n",[19,206,207],{},"getRouterParam"," sont auto-importés.",[14,210,212],{"id":211},"brancher-prisma","Brancher Prisma",[10,214,215],{},"Prisma est un ORM TypeScript : vous décrivez vos tables dans un schéma, il\ngénère un client typé et les migrations. Installation et schéma minimal :",[77,217,221],{"className":218,"code":219,"language":220,"meta":82,"style":82},"language-bash shiki shiki-themes github-dark","npm install -D prisma\nnpm install @prisma\u002Fclient\nnpx prisma init --datasource-provider sqlite\n","bash",[19,222,223,237,246],{"__ignoreMap":82},[86,224,225,228,231,234],{"class":88,"line":89},[86,226,227],{"class":106},"npm",[86,229,230],{"class":161}," install",[86,232,233],{"class":136}," -D",[86,235,236],{"class":161}," prisma\n",[86,238,239,241,243],{"class":88,"line":96},[86,240,227],{"class":106},[86,242,230],{"class":161},[86,244,245],{"class":161}," @prisma\u002Fclient\n",[86,247,248,251,254,257,260],{"class":88,"line":127},[86,249,250],{"class":106},"npx",[86,252,253],{"class":161}," prisma",[86,255,256],{"class":161}," init",[86,258,259],{"class":136}," --datasource-provider",[86,261,262],{"class":161}," sqlite\n",[77,264,268],{"className":265,"code":266,"language":267,"meta":82,"style":82},"language-prisma shiki shiki-themes github-dark","\u002F\u002F prisma\u002Fschema.prisma\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider = \"sqlite\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Article {\n  id      Int      @id @default(autoincrement())\n  titre   String\n  contenu String\n  publie  Boolean  @default(false)\n  cree_le DateTime @default(now())\n}\n","prisma",[19,269,270,275,285,295,300,306,317,327,347,352,357,368,391,400,408,427,445],{"__ignoreMap":82},[86,271,272],{"class":88,"line":89},[86,273,274],{"class":92},"\u002F\u002F prisma\u002Fschema.prisma\n",[86,276,277,280,283],{"class":88,"line":96},[86,278,279],{"class":99},"generator",[86,281,282],{"class":106}," client",[86,284,124],{"class":110},[86,286,287,290,292],{"class":88,"line":127},[86,288,289],{"class":110},"  provider ",[86,291,143],{"class":99},[86,293,294],{"class":161}," \"prisma-client-js\"\n",[86,296,297],{"class":88,"line":152},[86,298,299],{"class":110},"}\n",[86,301,302],{"class":88,"line":176},[86,303,305],{"emptyLinePlaceholder":304},true,"\n",[86,307,309,312,315],{"class":88,"line":308},6,[86,310,311],{"class":99},"datasource",[86,313,314],{"class":106}," db",[86,316,124],{"class":110},[86,318,320,322,324],{"class":88,"line":319},7,[86,321,289],{"class":110},[86,323,143],{"class":99},[86,325,326],{"class":161}," \"sqlite\"\n",[86,328,330,333,335,338,341,344],{"class":88,"line":329},8,[86,331,332],{"class":110},"  url      ",[86,334,143],{"class":99},[86,336,337],{"class":136}," env",[86,339,340],{"class":110},"(",[86,342,343],{"class":161},"\"DATABASE_URL\"",[86,345,346],{"class":110},")\n",[86,348,350],{"class":88,"line":349},9,[86,351,299],{"class":110},[86,353,355],{"class":88,"line":354},10,[86,356,305],{"emptyLinePlaceholder":304},[86,358,360,363,366],{"class":88,"line":359},11,[86,361,362],{"class":99},"model",[86,364,365],{"class":106}," Article",[86,367,124],{"class":110},[86,369,371,374,377,380,383,385,388],{"class":88,"line":370},12,[86,372,373],{"class":110},"  id      ",[86,375,376],{"class":136},"Int",[86,378,379],{"class":106},"      @id",[86,381,382],{"class":106}," @default",[86,384,340],{"class":110},[86,386,387],{"class":136},"autoincrement",[86,389,390],{"class":110},"())\n",[86,392,394,397],{"class":88,"line":393},13,[86,395,396],{"class":110},"  titre   ",[86,398,399],{"class":136},"String\n",[86,401,403,406],{"class":88,"line":402},14,[86,404,405],{"class":110},"  contenu ",[86,407,399],{"class":136},[86,409,411,414,417,420,422,425],{"class":88,"line":410},15,[86,412,413],{"class":110},"  publie  ",[86,415,416],{"class":136},"Boolean",[86,418,419],{"class":106},"  @default",[86,421,340],{"class":110},[86,423,424],{"class":136},"false",[86,426,346],{"class":110},[86,428,430,433,436,438,440,443],{"class":88,"line":429},16,[86,431,432],{"class":110},"  cree_le ",[86,434,435],{"class":136},"DateTime",[86,437,382],{"class":106},[86,439,340],{"class":110},[86,441,442],{"class":136},"now",[86,444,390],{"class":110},[86,446,448],{"class":88,"line":447},17,[86,449,299],{"class":110},[10,451,452],{},"On applique le schéma à la base et on génère le client :",[77,454,456],{"className":218,"code":455,"language":220,"meta":82,"style":82},"npx prisma migrate dev --name init\n",[19,457,458],{"__ignoreMap":82},[86,459,460,462,464,467,470,473],{"class":88,"line":89},[86,461,250],{"class":106},[86,463,253],{"class":161},[86,465,466],{"class":161}," migrate",[86,468,469],{"class":161}," dev",[86,471,472],{"class":136}," --name",[86,474,475],{"class":161}," init\n",[14,477,479],{"id":478},"le-client-prisma-en-singleton","Le client Prisma en singleton",[10,481,482,483,486],{},"Piège classique : instancier ",[19,484,485],{},"new PrismaClient()"," dans chaque route ouvre\nune nouvelle connexion à chaque appel et épuise la base. On l'instancie une\nseule fois, dans un utilitaire serveur réutilisable :",[77,488,490],{"className":79,"code":489,"language":81,"meta":82,"style":82},"\u002F\u002F server\u002Futils\u002Fprisma.ts\nimport { PrismaClient } from '@prisma\u002Fclient'\n\nexport const prisma = new PrismaClient()\n",[19,491,492,497,511,515],{"__ignoreMap":82},[86,493,494],{"class":88,"line":89},[86,495,496],{"class":92},"\u002F\u002F server\u002Futils\u002Fprisma.ts\n",[86,498,499,502,505,508],{"class":88,"line":96},[86,500,501],{"class":99},"import",[86,503,504],{"class":110}," { PrismaClient } ",[86,506,507],{"class":99},"from",[86,509,510],{"class":161}," '@prisma\u002Fclient'\n",[86,512,513],{"class":88,"line":127},[86,514,305],{"emptyLinePlaceholder":304},[86,516,517,519,522,524,527,530,533],{"class":88,"line":152},[86,518,100],{"class":99},[86,520,521],{"class":99}," const",[86,523,253],{"class":136},[86,525,526],{"class":99}," =",[86,528,529],{"class":99}," new",[86,531,532],{"class":106}," PrismaClient",[86,534,535],{"class":110},"()\n",[10,537,538,539,542],{},"Placé dans ",[19,540,541],{},"server\u002Futils\u002F",", il est auto-importé dans toutes les routes\nserveur. Une instance, une réserve de connexions, partagée proprement.",[14,544,546],{"id":545},"une-vraie-route-qui-lit-la-base","Une vraie route qui lit la base",[77,548,550],{"className":79,"code":549,"language":81,"meta":82,"style":82},"\u002F\u002F server\u002Fapi\u002Farticles.get.ts\nexport default defineEventHandler(async () => {\n  return await prisma.article.findMany({\n    where: { publie: true },\n    orderBy: { cree_le: 'desc' }\n  })\n})\n",[19,551,552,557,577,593,604,614,619],{"__ignoreMap":82},[86,553,554],{"class":88,"line":89},[86,555,556],{"class":92},"\u002F\u002F server\u002Fapi\u002Farticles.get.ts\n",[86,558,559,561,563,565,567,570,573,575],{"class":88,"line":96},[86,560,100],{"class":99},[86,562,103],{"class":99},[86,564,107],{"class":106},[86,566,340],{"class":110},[86,568,569],{"class":99},"async",[86,571,572],{"class":110}," () ",[86,574,121],{"class":99},[86,576,124],{"class":110},[86,578,579,581,584,587,590],{"class":88,"line":127},[86,580,155],{"class":99},[86,582,583],{"class":99}," await",[86,585,586],{"class":110}," prisma.article.",[86,588,589],{"class":106},"findMany",[86,591,592],{"class":110},"({\n",[86,594,595,598,601],{"class":88,"line":152},[86,596,597],{"class":110},"    where: { publie: ",[86,599,600],{"class":136},"true",[86,602,603],{"class":110}," },\n",[86,605,606,609,612],{"class":88,"line":176},[86,607,608],{"class":110},"    orderBy: { cree_le: ",[86,610,611],{"class":161},"'desc'",[86,613,173],{"class":110},[86,615,616],{"class":88,"line":308},[86,617,618],{"class":110},"  })\n",[86,620,621],{"class":88,"line":319},[86,622,179],{"class":110},[10,624,625],{},"Et une route qui écrit, avec validation de l'entrée :",[77,627,629],{"className":79,"code":628,"language":81,"meta":82,"style":82},"\u002F\u002F server\u002Fapi\u002Farticles.post.ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  if (!body?.titre || !body?.contenu) {\n    throw createError({ statusCode: 400, message: 'Titre et contenu requis' })\n  }\n\n  return await prisma.article.create({\n    data: { titre: body.titre, contenu: body.contenu }\n  })\n})\n",[19,630,631,636,659,675,679,701,724,729,733,746,751,755],{"__ignoreMap":82},[86,632,633],{"class":88,"line":89},[86,634,635],{"class":92},"\u002F\u002F server\u002Fapi\u002Farticles.post.ts\n",[86,637,638,640,642,644,646,648,651,653,655,657],{"class":88,"line":96},[86,639,100],{"class":99},[86,641,103],{"class":99},[86,643,107],{"class":106},[86,645,340],{"class":110},[86,647,569],{"class":99},[86,649,650],{"class":110}," (",[86,652,115],{"class":114},[86,654,118],{"class":110},[86,656,121],{"class":99},[86,658,124],{"class":110},[86,660,661,663,666,668,670,673],{"class":88,"line":127},[86,662,130],{"class":99},[86,664,665],{"class":136}," body",[86,667,526],{"class":99},[86,669,583],{"class":99},[86,671,672],{"class":106}," readBody",[86,674,149],{"class":110},[86,676,677],{"class":88,"line":152},[86,678,305],{"emptyLinePlaceholder":304},[86,680,681,684,686,689,692,695,698],{"class":88,"line":176},[86,682,683],{"class":99},"  if",[86,685,650],{"class":110},[86,687,688],{"class":99},"!",[86,690,691],{"class":110},"body?.titre ",[86,693,694],{"class":99},"||",[86,696,697],{"class":99}," !",[86,699,700],{"class":110},"body?.contenu) {\n",[86,702,703,706,709,712,715,718,721],{"class":88,"line":308},[86,704,705],{"class":99},"    throw",[86,707,708],{"class":106}," createError",[86,710,711],{"class":110},"({ statusCode: ",[86,713,714],{"class":136},"400",[86,716,717],{"class":110},", message: ",[86,719,720],{"class":161},"'Titre et contenu requis'",[86,722,723],{"class":110}," })\n",[86,725,726],{"class":88,"line":319},[86,727,728],{"class":110},"  }\n",[86,730,731],{"class":88,"line":329},[86,732,305],{"emptyLinePlaceholder":304},[86,734,735,737,739,741,744],{"class":88,"line":349},[86,736,155],{"class":99},[86,738,583],{"class":99},[86,740,586],{"class":110},[86,742,743],{"class":106},"create",[86,745,592],{"class":110},[86,747,748],{"class":88,"line":354},[86,749,750],{"class":110},"    data: { titre: body.titre, contenu: body.contenu }\n",[86,752,753],{"class":88,"line":359},[86,754,618],{"class":110},[86,756,757],{"class":88,"line":370},[86,758,179],{"class":110},[10,760,761,764,765,768],{},[19,762,763],{},"createError"," renvoie une vraie erreur HTTP, que le client saura interpréter\nplutôt que de recevoir un ",[19,766,767],{},"200"," avec un objet d'erreur déguisé.",[14,770,772],{"id":771},"le-front-consomme-sa-propre-api","Le front consomme sa propre API",[10,774,775,776,779],{},"Côté page, on ne fait pas d'appel réseau exotique : la même application\nappelle sa route avec ",[19,777,778],{},"useFetch",". L'URL est relative, il n'y a pas de\nproblème de CORS, et le rendu serveur pré-remplit déjà les données.",[77,781,785],{"className":782,"code":783,"language":784,"meta":82,"style":82},"language-vue shiki shiki-themes github-dark","\u003Cscript setup lang=\"ts\">\nconst { data: articles, refresh } = await useFetch('\u002Fapi\u002Farticles')\n\nasync function publier(nouvel: { titre: string; contenu: string }) {\n  await $fetch('\u002Fapi\u002Farticles', { method: 'POST', body: nouvel })\n  await refresh()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Carticle v-for=\"a in articles\" :key=\"a.id\">\n    \u003Ch2>{{ a.titre }}\u003C\u002Fh2>\n    \u003Cp>{{ a.contenu }}\u003C\u002Fp>\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n","vue",[19,786,787,810,847,851,892,913,922,926,935,939,948,974,988,1001,1010],{"__ignoreMap":82},[86,788,789,792,796,799,802,804,807],{"class":88,"line":89},[86,790,791],{"class":110},"\u003C",[86,793,795],{"class":794},"s4JwU","script",[86,797,798],{"class":106}," setup",[86,800,801],{"class":106}," lang",[86,803,143],{"class":110},[86,805,806],{"class":161},"\"ts\"",[86,808,809],{"class":110},">\n",[86,811,812,815,817,820,823,826,828,831,833,835,837,840,842,845],{"class":88,"line":96},[86,813,814],{"class":99},"const",[86,816,133],{"class":110},[86,818,819],{"class":114},"data",[86,821,822],{"class":110},": ",[86,824,825],{"class":136},"articles",[86,827,71],{"class":110},[86,829,830],{"class":136},"refresh",[86,832,140],{"class":110},[86,834,143],{"class":99},[86,836,583],{"class":99},[86,838,839],{"class":106}," useFetch",[86,841,340],{"class":110},[86,843,844],{"class":161},"'\u002Fapi\u002Farticles'",[86,846,346],{"class":110},[86,848,849],{"class":88,"line":127},[86,850,305],{"emptyLinePlaceholder":304},[86,852,853,855,858,861,863,866,869,871,874,876,879,882,885,887,889],{"class":88,"line":152},[86,854,569],{"class":99},[86,856,857],{"class":99}," function",[86,859,860],{"class":106}," publier",[86,862,340],{"class":110},[86,864,865],{"class":114},"nouvel",[86,867,868],{"class":99},":",[86,870,133],{"class":110},[86,872,873],{"class":114},"titre",[86,875,868],{"class":99},[86,877,878],{"class":136}," string",[86,880,881],{"class":110},"; ",[86,883,884],{"class":114},"contenu",[86,886,868],{"class":99},[86,888,878],{"class":136},[86,890,891],{"class":110}," }) {\n",[86,893,894,897,900,902,904,907,910],{"class":88,"line":176},[86,895,896],{"class":99},"  await",[86,898,899],{"class":106}," $fetch",[86,901,340],{"class":110},[86,903,844],{"class":161},[86,905,906],{"class":110},", { method: ",[86,908,909],{"class":161},"'POST'",[86,911,912],{"class":110},", body: nouvel })\n",[86,914,915,917,920],{"class":88,"line":308},[86,916,896],{"class":99},[86,918,919],{"class":106}," refresh",[86,921,535],{"class":110},[86,923,924],{"class":88,"line":319},[86,925,299],{"class":110},[86,927,928,931,933],{"class":88,"line":329},[86,929,930],{"class":110},"\u003C\u002F",[86,932,795],{"class":794},[86,934,809],{"class":110},[86,936,937],{"class":88,"line":349},[86,938,305],{"emptyLinePlaceholder":304},[86,940,941,943,946],{"class":88,"line":354},[86,942,791],{"class":110},[86,944,945],{"class":794},"template",[86,947,809],{"class":110},[86,949,950,953,956,959,961,964,967,969,972],{"class":88,"line":359},[86,951,952],{"class":110},"  \u003C",[86,954,955],{"class":794},"article",[86,957,958],{"class":106}," v-for",[86,960,143],{"class":110},[86,962,963],{"class":161},"\"a in articles\"",[86,965,966],{"class":106}," :key",[86,968,143],{"class":110},[86,970,971],{"class":161},"\"a.id\"",[86,973,809],{"class":110},[86,975,976,979,981,984,986],{"class":88,"line":370},[86,977,978],{"class":110},"    \u003C",[86,980,14],{"class":794},[86,982,983],{"class":110},">{{ a.titre }}\u003C\u002F",[86,985,14],{"class":794},[86,987,809],{"class":110},[86,989,990,992,994,997,999],{"class":88,"line":393},[86,991,978],{"class":110},[86,993,10],{"class":794},[86,995,996],{"class":110},">{{ a.contenu }}\u003C\u002F",[86,998,10],{"class":794},[86,1000,809],{"class":110},[86,1002,1003,1006,1008],{"class":88,"line":402},[86,1004,1005],{"class":110},"  \u003C\u002F",[86,1007,955],{"class":794},[86,1009,809],{"class":110},[86,1011,1012,1014,1016],{"class":88,"line":410},[86,1013,930],{"class":110},[86,1015,945],{"class":794},[86,1017,809],{"class":110},[10,1019,1020,1022,1023,1026],{},[19,1021,778],{}," pour lire au rendu (avec cache et hydratation), ",[19,1024,1025],{},"$fetch"," pour les\nactions ponctuelles comme un POST. La boucle est complète : la page lit une\nroute Nitro, qui interroge Prisma, qui parle à la base, et tout est typé du\nmême bout à l'autre.",[14,1028,1030],{"id":1029},"ce-que-ça-change-au-déploiement","Ce que ça change au déploiement",[10,1032,1033],{},"Attention : dès qu'il y a des routes serveur et une base, on quitte le monde\npurement statique. Il faut une cible capable d'exécuter du Node (un VPS, une\nplateforme applicative, une fonction serverless), et non plus un simple\nhébergement de fichiers. Nitro sait produire la bonne sortie pour chacune de\nces cibles, mais le choix d'hébergement, lui, n'est plus le même que pour un\nsite statique. C'est le prix, assumé, du fullstack.",[14,1035,1037],{"id":1036},"la-leçon","La leçon",[10,1039,1040,1041,1043],{},"Nuxt fullstack, ce n'est pas « Nuxt plus un backend » : c'est un seul\nprojet, un seul langage, une seule chaîne de types, du composant Vue jusqu'à\nla table SQL. Nitro fournit le serveur, Prisma la base, et ",[19,1042,778],{}," relie\nles deux sans couture. Pour un produit mené en petite équipe, cette unité\nest un avantage décisif : moins de code de plomberie, moins de contrats\nd'API à maintenir, et le remboursement se fait sur chaque fonctionnalité.",[1045,1046,1047],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":82,"searchDepth":96,"depth":96,"links":1049},[1050,1052,1053,1054,1055,1056,1057],{"id":16,"depth":96,"text":1051},"Le dossier server\u002F",{"id":211,"depth":96,"text":212},{"id":478,"depth":96,"text":479},{"id":545,"depth":96,"text":546},{"id":771,"depth":96,"text":772},{"id":1029,"depth":96,"text":1030},{"id":1036,"depth":96,"text":1037},false,"2026-07-18","Construire l'API dans le même projet que le front : les routes serveur de Nitro, Prisma pour la base de données, et useFetch côté client. Un seul dépôt, une seule langue.","md",null,{},"\u002Fblog\u002Fapp-fullstack-nuxt-nitro-prisma",{"title":5,"description":1060},"blog\u002Fapp-fullstack-nuxt-nitro-prisma",[1068,1069,1070],"Nuxt","Prisma","Fullstack","t2G8Y9IU16PitwCOlzmh_bflZslEo8SfxVkYuzq7oA4",1784652014639]