[{"data":1,"prerenderedAt":1108},["ShallowReactive",2],{"page-/post/nuxt/nitro/standard-response-global-error-handler":3,"surrounding-page":1099},{"id":4,"title":5,"author":6,"body":7,"date":1085,"description":1086,"extension":1087,"group":6,"lastmod":1085,"meta":1088,"navigation":75,"path":1089,"rawbody":1090,"seo":1091,"showTitle":6,"stem":1092,"tags":1093,"versions":1095,"__hash__":1098},"content/post/nuxt/nitro/standard-response-global-error-handler.md","Nuxt 全栈开发·自定义响应和全局错误处理",null,{"type":8,"value":9,"toc":1080},"minimark",[10,23,28,37,339,354,572,576,582,588,761,767,871,874,878,884,898,906,964,1070,1073,1076],[11,12,13,14,18,19,22],"p",{},"使用 ",[15,16,17],"code",{},"Nuxt"," 全栈开发时，如何像 ",[15,20,21],{},"NestJS"," 一样优雅的设置统一的响应体，以及如何捕获全局 Error",[24,25,27],"h2",{"id":26},"自定义-handler","自定义 Handler",[11,29,30],{},[31,32,36],"a",{"href":33,"rel":34},"https://nuxt.com/docs/4.x/guide/directory-structure/server#server-utilities",[35],"nofollow","官方文档",[38,39,45],"pre",{"className":40,"code":41,"filename":42,"language":43,"meta":44,"style":44},"language-typescript shiki shiki-themes github-light","import type { EventHandler, EventHandlerRequest } from 'h3'\n\nexport const defineWrappedResponseHandler = \u003CT extends EventHandlerRequest, D> (\n  handler: EventHandler\u003CT, D>\n): EventHandler\u003CT, D> =>\n  defineEventHandler\u003CT>(async event => {\n    try {\n      // 拿到接口文件里返回的响应值\n      const response = await handler(event)\n      // 自定义统一的结构\n      // 自定义code，自定义 message\n      return { code: 200, data: response, message: 'ok' }\n    } catch (err) {\n      // 自定义错误响应体\n      return { code: 500, message: 'error' + err }\n      // 或直接抛出错误\n      // throw createError({\n      //  statusCode: 500,\n      //  message: '出错啦，请稍后再试～',\n      //})\n    }\n  })\n","server/utils/handler.ts","typescript","",[15,46,47,70,77,114,139,163,188,196,203,224,230,236,257,269,275,297,303,309,315,321,327,333],{"__ignoreMap":44},[48,49,52,56,59,63,66],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sD7c4","import",[48,57,58],{"class":54}," type",[48,60,62],{"class":61},"sgsFI"," { EventHandler, EventHandlerRequest } ",[48,64,65],{"class":54},"from",[48,67,69],{"class":68},"sYBdl"," 'h3'\n",[48,71,73],{"class":50,"line":72},2,[48,74,76],{"emptyLinePlaceholder":75},true,"\n",[48,78,80,83,86,90,93,96,99,102,105,108,111],{"class":50,"line":79},3,[48,81,82],{"class":54},"export",[48,84,85],{"class":54}," const",[48,87,89],{"class":88},"s7eDp"," defineWrappedResponseHandler",[48,91,92],{"class":54}," =",[48,94,95],{"class":61}," \u003C",[48,97,98],{"class":88},"T",[48,100,101],{"class":54}," extends",[48,103,104],{"class":88}," EventHandlerRequest",[48,106,107],{"class":61},", ",[48,109,110],{"class":88},"D",[48,112,113],{"class":61},"> (\n",[48,115,117,121,124,127,130,132,134,136],{"class":50,"line":116},4,[48,118,120],{"class":119},"sqxcx","  handler",[48,122,123],{"class":54},":",[48,125,126],{"class":88}," EventHandler",[48,128,129],{"class":61},"\u003C",[48,131,98],{"class":88},[48,133,107],{"class":61},[48,135,110],{"class":88},[48,137,138],{"class":61},">\n",[48,140,142,145,147,149,151,153,155,157,160],{"class":50,"line":141},5,[48,143,144],{"class":61},")",[48,146,123],{"class":54},[48,148,126],{"class":88},[48,150,129],{"class":61},[48,152,98],{"class":88},[48,154,107],{"class":61},[48,156,110],{"class":88},[48,158,159],{"class":61},"> ",[48,161,162],{"class":54},"=>\n",[48,164,166,169,171,173,176,179,182,185],{"class":50,"line":165},6,[48,167,168],{"class":88},"  defineEventHandler",[48,170,129],{"class":61},[48,172,98],{"class":88},[48,174,175],{"class":61},">(",[48,177,178],{"class":54},"async",[48,180,181],{"class":119}," event",[48,183,184],{"class":54}," =>",[48,186,187],{"class":61}," {\n",[48,189,191,194],{"class":50,"line":190},7,[48,192,193],{"class":54},"    try",[48,195,187],{"class":61},[48,197,199],{"class":50,"line":198},8,[48,200,202],{"class":201},"sAwPA","      // 拿到接口文件里返回的响应值\n",[48,204,206,209,213,215,218,221],{"class":50,"line":205},9,[48,207,208],{"class":54},"      const",[48,210,212],{"class":211},"sYu0t"," response",[48,214,92],{"class":54},[48,216,217],{"class":54}," await",[48,219,220],{"class":88}," handler",[48,222,223],{"class":61},"(event)\n",[48,225,227],{"class":50,"line":226},10,[48,228,229],{"class":201},"      // 自定义统一的结构\n",[48,231,233],{"class":50,"line":232},11,[48,234,235],{"class":201},"      // 自定义code，自定义 message\n",[48,237,239,242,245,248,251,254],{"class":50,"line":238},12,[48,240,241],{"class":54},"      return",[48,243,244],{"class":61}," { code: ",[48,246,247],{"class":211},"200",[48,249,250],{"class":61},", data: response, message: ",[48,252,253],{"class":68},"'ok'",[48,255,256],{"class":61}," }\n",[48,258,260,263,266],{"class":50,"line":259},13,[48,261,262],{"class":61},"    } ",[48,264,265],{"class":54},"catch",[48,267,268],{"class":61}," (err) {\n",[48,270,272],{"class":50,"line":271},14,[48,273,274],{"class":201},"      // 自定义错误响应体\n",[48,276,278,280,282,285,288,291,294],{"class":50,"line":277},15,[48,279,241],{"class":54},[48,281,244],{"class":61},[48,283,284],{"class":211},"500",[48,286,287],{"class":61},", message: ",[48,289,290],{"class":68},"'error'",[48,292,293],{"class":54}," +",[48,295,296],{"class":61}," err }\n",[48,298,300],{"class":50,"line":299},16,[48,301,302],{"class":201},"      // 或直接抛出错误\n",[48,304,306],{"class":50,"line":305},17,[48,307,308],{"class":201},"      // throw createError({\n",[48,310,312],{"class":50,"line":311},18,[48,313,314],{"class":201},"      //  statusCode: 500,\n",[48,316,318],{"class":50,"line":317},19,[48,319,320],{"class":201},"      //  message: '出错啦，请稍后再试～',\n",[48,322,324],{"class":50,"line":323},20,[48,325,326],{"class":201},"      //})\n",[48,328,330],{"class":50,"line":329},21,[48,331,332],{"class":61},"    }\n",[48,334,336],{"class":50,"line":335},22,[48,337,338],{"class":61},"  })\n",[11,340,341,342,345,346,349,350,353],{},"使用时将 ",[15,343,344],{},"defineEventHandler"," 替换为 ",[15,347,348],{},"defineWrappedResponseHandler"," ，同时直接 ",[15,351,352],{},"return data"," 。",[38,355,358],{"className":40,"code":356,"filename":357,"language":43,"meta":44,"style":44},"export default defineWrappedResponseHandler(async (event) => {\n  const schema = z.object({\n    id: z.string(),\n    user_id: z.string().optional(),\n  })\n  const query = await useSafeValidatedQuery(event, schema)\n\n  if (!query.success) {\n    throw createError({\n      statusCode: 400,\n      statusMessage: (query as any).message ?? '参数错误',\n    })\n  }\n\n  // 伪代码\n  const data =  await prisma.findMany()\n  \n  return data\n})\n","server/api/demo.ts",[15,359,360,388,407,418,433,437,454,458,471,481,492,514,519,524,528,533,554,559,567],{"__ignoreMap":44},[48,361,362,364,367,369,372,374,377,380,383,386],{"class":50,"line":51},[48,363,82],{"class":54},[48,365,366],{"class":54}," default",[48,368,89],{"class":88},[48,370,371],{"class":61},"(",[48,373,178],{"class":54},[48,375,376],{"class":61}," (",[48,378,379],{"class":119},"event",[48,381,382],{"class":61},") ",[48,384,385],{"class":54},"=>",[48,387,187],{"class":61},[48,389,390,393,396,398,401,404],{"class":50,"line":72},[48,391,392],{"class":54},"  const",[48,394,395],{"class":211}," schema",[48,397,92],{"class":54},[48,399,400],{"class":61}," z.",[48,402,403],{"class":88},"object",[48,405,406],{"class":61},"({\n",[48,408,409,412,415],{"class":50,"line":79},[48,410,411],{"class":61},"    id: z.",[48,413,414],{"class":88},"string",[48,416,417],{"class":61},"(),\n",[48,419,420,423,425,428,431],{"class":50,"line":116},[48,421,422],{"class":61},"    user_id: z.",[48,424,414],{"class":88},[48,426,427],{"class":61},"().",[48,429,430],{"class":88},"optional",[48,432,417],{"class":61},[48,434,435],{"class":50,"line":141},[48,436,338],{"class":61},[48,438,439,441,444,446,448,451],{"class":50,"line":165},[48,440,392],{"class":54},[48,442,443],{"class":211}," query",[48,445,92],{"class":54},[48,447,217],{"class":54},[48,449,450],{"class":88}," useSafeValidatedQuery",[48,452,453],{"class":61},"(event, schema)\n",[48,455,456],{"class":50,"line":190},[48,457,76],{"emptyLinePlaceholder":75},[48,459,460,463,465,468],{"class":50,"line":198},[48,461,462],{"class":54},"  if",[48,464,376],{"class":61},[48,466,467],{"class":54},"!",[48,469,470],{"class":61},"query.success) {\n",[48,472,473,476,479],{"class":50,"line":205},[48,474,475],{"class":54},"    throw",[48,477,478],{"class":88}," createError",[48,480,406],{"class":61},[48,482,483,486,489],{"class":50,"line":226},[48,484,485],{"class":61},"      statusCode: ",[48,487,488],{"class":211},"400",[48,490,491],{"class":61},",\n",[48,493,494,497,500,503,506,509,512],{"class":50,"line":232},[48,495,496],{"class":61},"      statusMessage: (query ",[48,498,499],{"class":54},"as",[48,501,502],{"class":211}," any",[48,504,505],{"class":61},").message ",[48,507,508],{"class":54},"??",[48,510,511],{"class":68}," '参数错误'",[48,513,491],{"class":61},[48,515,516],{"class":50,"line":238},[48,517,518],{"class":61},"    })\n",[48,520,521],{"class":50,"line":259},[48,522,523],{"class":61},"  }\n",[48,525,526],{"class":50,"line":271},[48,527,76],{"emptyLinePlaceholder":75},[48,529,530],{"class":50,"line":277},[48,531,532],{"class":201},"  // 伪代码\n",[48,534,535,537,540,542,545,548,551],{"class":50,"line":299},[48,536,392],{"class":54},[48,538,539],{"class":211}," data",[48,541,92],{"class":54},[48,543,544],{"class":54},"  await",[48,546,547],{"class":61}," prisma.",[48,549,550],{"class":88},"findMany",[48,552,553],{"class":61},"()\n",[48,555,556],{"class":50,"line":305},[48,557,558],{"class":61},"  \n",[48,560,561,564],{"class":50,"line":311},[48,562,563],{"class":54},"  return",[48,565,566],{"class":61}," data\n",[48,568,569],{"class":50,"line":317},[48,570,571],{"class":61},"})\n",[24,573,575],{"id":574},"在-plugins-中使用-hook","在 plugins 中使用 hook",[11,577,578],{},[31,579,36],{"href":580,"rel":581},"https://nitro.build/guide/plugins#request-and-response-lifecycle",[35],[11,583,584,587],{},[15,585,586],{},"nitro"," 中有多个生命周期钩子可供自定义，可自行设置标准响应体，打印日志等",[38,589,592],{"className":40,"code":590,"filename":591,"language":43,"meta":44,"style":44},"export default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook(\"request\", (event) => {\n    console.log(\"on request\", event.path);\n  });\n\n  nitroApp.hooks.hook(\"beforeResponse\", (event, { body }) => {\n    console.log(\"on response\", event.path, { body });\n  });\n\n  nitroApp.hooks.hook(\"afterResponse\", (event, { body }) => {\n    console.log(\"on after response\", event.path, { body });\n  });\n});\n","server/plugins/response.ts",[15,593,594,615,639,655,660,664,692,706,710,714,739,752,756],{"__ignoreMap":44},[48,595,596,598,600,603,606,609,611,613],{"class":50,"line":51},[48,597,82],{"class":54},[48,599,366],{"class":54},[48,601,602],{"class":88}," defineNitroPlugin",[48,604,605],{"class":61},"((",[48,607,608],{"class":119},"nitroApp",[48,610,382],{"class":61},[48,612,385],{"class":54},[48,614,187],{"class":61},[48,616,617,620,623,625,628,631,633,635,637],{"class":50,"line":72},[48,618,619],{"class":61},"  nitroApp.hooks.",[48,621,622],{"class":88},"hook",[48,624,371],{"class":61},[48,626,627],{"class":68},"\"request\"",[48,629,630],{"class":61},", (",[48,632,379],{"class":119},[48,634,382],{"class":61},[48,636,385],{"class":54},[48,638,187],{"class":61},[48,640,641,644,647,649,652],{"class":50,"line":79},[48,642,643],{"class":61},"    console.",[48,645,646],{"class":88},"log",[48,648,371],{"class":61},[48,650,651],{"class":68},"\"on request\"",[48,653,654],{"class":61},", event.path);\n",[48,656,657],{"class":50,"line":116},[48,658,659],{"class":61},"  });\n",[48,661,662],{"class":50,"line":141},[48,663,76],{"emptyLinePlaceholder":75},[48,665,666,668,670,672,675,677,679,682,685,688,690],{"class":50,"line":165},[48,667,619],{"class":61},[48,669,622],{"class":88},[48,671,371],{"class":61},[48,673,674],{"class":68},"\"beforeResponse\"",[48,676,630],{"class":61},[48,678,379],{"class":119},[48,680,681],{"class":61},", { ",[48,683,684],{"class":119},"body",[48,686,687],{"class":61}," }) ",[48,689,385],{"class":54},[48,691,187],{"class":61},[48,693,694,696,698,700,703],{"class":50,"line":190},[48,695,643],{"class":61},[48,697,646],{"class":88},[48,699,371],{"class":61},[48,701,702],{"class":68},"\"on response\"",[48,704,705],{"class":61},", event.path, { body });\n",[48,707,708],{"class":50,"line":198},[48,709,659],{"class":61},[48,711,712],{"class":50,"line":205},[48,713,76],{"emptyLinePlaceholder":75},[48,715,716,718,720,722,725,727,729,731,733,735,737],{"class":50,"line":226},[48,717,619],{"class":61},[48,719,622],{"class":88},[48,721,371],{"class":61},[48,723,724],{"class":68},"\"afterResponse\"",[48,726,630],{"class":61},[48,728,379],{"class":119},[48,730,681],{"class":61},[48,732,684],{"class":119},[48,734,687],{"class":61},[48,736,385],{"class":54},[48,738,187],{"class":61},[48,740,741,743,745,747,750],{"class":50,"line":232},[48,742,643],{"class":61},[48,744,646],{"class":88},[48,746,371],{"class":61},[48,748,749],{"class":68},"\"on after response\"",[48,751,705],{"class":61},[48,753,754],{"class":50,"line":238},[48,755,659],{"class":61},[48,757,758],{"class":50,"line":259},[48,759,760],{"class":61},"});\n",[11,762,13,763,766],{},[15,764,765],{},"beforeResponse"," 实现自定义响应体",[38,768,770],{"className":40,"code":769,"filename":591,"language":43,"meta":44,"style":44},"export default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook(\"beforeResponse\", (event, { body }) => {\n    if (t?.body) {\n        t.body = {\n            code: 200,\n            data: t.body,\n            message: 'ok'\n        }\n    }\n  });\n});\n",[15,771,772,790,814,822,832,841,846,854,859,863,867],{"__ignoreMap":44},[48,773,774,776,778,780,782,784,786,788],{"class":50,"line":51},[48,775,82],{"class":54},[48,777,366],{"class":54},[48,779,602],{"class":88},[48,781,605],{"class":61},[48,783,608],{"class":119},[48,785,382],{"class":61},[48,787,385],{"class":54},[48,789,187],{"class":61},[48,791,792,794,796,798,800,802,804,806,808,810,812],{"class":50,"line":72},[48,793,619],{"class":61},[48,795,622],{"class":88},[48,797,371],{"class":61},[48,799,674],{"class":68},[48,801,630],{"class":61},[48,803,379],{"class":119},[48,805,681],{"class":61},[48,807,684],{"class":119},[48,809,687],{"class":61},[48,811,385],{"class":54},[48,813,187],{"class":61},[48,815,816,819],{"class":50,"line":79},[48,817,818],{"class":54},"    if",[48,820,821],{"class":61}," (t?.body) {\n",[48,823,824,827,830],{"class":50,"line":116},[48,825,826],{"class":61},"        t.body ",[48,828,829],{"class":54},"=",[48,831,187],{"class":61},[48,833,834,837,839],{"class":50,"line":141},[48,835,836],{"class":61},"            code: ",[48,838,247],{"class":211},[48,840,491],{"class":61},[48,842,843],{"class":50,"line":165},[48,844,845],{"class":61},"            data: t.body,\n",[48,847,848,851],{"class":50,"line":190},[48,849,850],{"class":61},"            message: ",[48,852,853],{"class":68},"'ok'\n",[48,855,856],{"class":50,"line":198},[48,857,858],{"class":61},"        }\n",[48,860,861],{"class":50,"line":205},[48,862,332],{"class":61},[48,864,865],{"class":50,"line":226},[48,866,659],{"class":61},[48,868,869],{"class":50,"line":232},[48,870,760],{"class":61},[11,872,873],{},"以上就是两种设置统一响应体的方式",[24,875,877],{"id":876},"errorhandler","errorHandler",[11,879,880],{},[31,881,36],{"href":882,"rel":883},"https://nitro.build/config#errorhandler",[35],[885,886,887],"blockquote",{},[11,888,889,890,893,894,897],{},"Path to a custom runtime error handler. Replacing nitro's built-in error page. The error handler is given an ",[15,891,892],{},"H3Error"," and ",[15,895,896],{},"H3Event",". If the handler returns a promise it is awaited. The handler is expected to send a response of its own. Below is an example where a plain-text response is returned using h3's functions.",[11,899,900,902,903,905],{},[15,901,586],{}," 中有一个默认的错误页面，你可以自定义自己的 ",[15,904,877],{}," 用来和统一响应体",[38,907,910],{"className":40,"code":908,"filename":909,"language":43,"meta":44,"style":44},"export default defineNuxtConfig({\n    future: {\n        compatibilityVersion: 4,\n    },\n    nitro: {\n        errorHandler: '~~/server/error'\n    }\n})\n","nuxt.config.ts",[15,911,912,923,928,938,943,948,956,960],{"__ignoreMap":44},[48,913,914,916,918,921],{"class":50,"line":51},[48,915,82],{"class":54},[48,917,366],{"class":54},[48,919,920],{"class":88}," defineNuxtConfig",[48,922,406],{"class":61},[48,924,925],{"class":50,"line":72},[48,926,927],{"class":61},"    future: {\n",[48,929,930,933,936],{"class":50,"line":79},[48,931,932],{"class":61},"        compatibilityVersion: ",[48,934,935],{"class":211},"4",[48,937,491],{"class":61},[48,939,940],{"class":50,"line":116},[48,941,942],{"class":61},"    },\n",[48,944,945],{"class":50,"line":141},[48,946,947],{"class":61},"    nitro: {\n",[48,949,950,953],{"class":50,"line":165},[48,951,952],{"class":61},"        errorHandler: ",[48,954,955],{"class":68},"'~~/server/error'\n",[48,957,958],{"class":50,"line":190},[48,959,332],{"class":61},[48,961,962],{"class":50,"line":198},[48,963,571],{"class":61},[38,965,968],{"className":40,"code":966,"filename":967,"language":43,"meta":44,"style":44},"export default defineNitroErrorHandler((error, event) => {\n  setResponseHeader(event, 'Content-Type', 'application/json')\n  console.log(`[${new Date().toLocaleDateString()}]-[nitro error]: `, error)\n  return send(event, { data: null, message: '服务器异常' })\n})\n","server/error.ts",[15,969,970,994,1013,1045,1066],{"__ignoreMap":44},[48,971,972,974,976,979,981,984,986,988,990,992],{"class":50,"line":51},[48,973,82],{"class":54},[48,975,366],{"class":54},[48,977,978],{"class":88}," defineNitroErrorHandler",[48,980,605],{"class":61},[48,982,983],{"class":119},"error",[48,985,107],{"class":61},[48,987,379],{"class":119},[48,989,382],{"class":61},[48,991,385],{"class":54},[48,993,187],{"class":61},[48,995,996,999,1002,1005,1007,1010],{"class":50,"line":72},[48,997,998],{"class":88},"  setResponseHeader",[48,1000,1001],{"class":61},"(event, ",[48,1003,1004],{"class":68},"'Content-Type'",[48,1006,107],{"class":61},[48,1008,1009],{"class":68},"'application/json'",[48,1011,1012],{"class":61},")\n",[48,1014,1015,1018,1020,1022,1025,1028,1031,1033,1036,1039,1042],{"class":50,"line":79},[48,1016,1017],{"class":61},"  console.",[48,1019,646],{"class":88},[48,1021,371],{"class":61},[48,1023,1024],{"class":68},"`[${",[48,1026,1027],{"class":54},"new",[48,1029,1030],{"class":88}," Date",[48,1032,427],{"class":68},[48,1034,1035],{"class":88},"toLocaleDateString",[48,1037,1038],{"class":68},"()",[48,1040,1041],{"class":68},"}]-[nitro error]: `",[48,1043,1044],{"class":61},", error)\n",[48,1046,1047,1049,1052,1055,1058,1060,1063],{"class":50,"line":116},[48,1048,563],{"class":54},[48,1050,1051],{"class":88}," send",[48,1053,1054],{"class":61},"(event, { data: ",[48,1056,1057],{"class":211},"null",[48,1059,287],{"class":61},[48,1061,1062],{"class":68},"'服务器异常'",[48,1064,1065],{"class":61}," })\n",[48,1067,1068],{"class":50,"line":141},[48,1069,571],{"class":61},[11,1071,1072],{},"以上就是群里小伙伴们各自实践中得到的经验！",[11,1074,1075],{},"希望对你有所帮助☺️",[1077,1078,1079],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}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);}",{"title":44,"searchDepth":72,"depth":72,"links":1081},[1082,1083,1084],{"id":26,"depth":72,"text":27},{"id":574,"depth":72,"text":575},{"id":876,"depth":72,"text":877},"2025-06-24T00:00:00.000Z","使用 Nuxt 全栈开发时，如何像NestJS 一样优雅的设置统一的响应体，以及如何捕获全局 Error","md",{},"/post/nuxt/nitro/standard-response-global-error-handler","---\ntitle: Nuxt 全栈开发·自定义响应和全局错误处理\ndate: 2025-06-24\nlastmod: 2025-06-24\ntags: [\"Nuxt\", \"Nitro\"]\nversions: [\"nuxt@3.17.2\", \"nitro@2.11.12\"]\ndescription: 使用 Nuxt 全栈开发时，如何像NestJS 一样优雅的设置统一的响应体，以及如何捕获全局 Error\n---\n使用 `Nuxt` 全栈开发时，如何像 `NestJS` 一样优雅的设置统一的响应体，以及如何捕获全局 Error\n## 自定义 Handler\n\n[官方文档](https://nuxt.com/docs/4.x/guide/directory-structure/server#server-utilities)\n\n```typescript [server/utils/handler.ts]\nimport type { EventHandler, EventHandlerRequest } from 'h3'\n\nexport const defineWrappedResponseHandler = \u003CT extends EventHandlerRequest, D> (\n  handler: EventHandler\u003CT, D>\n): EventHandler\u003CT, D> =>\n  defineEventHandler\u003CT>(async event => {\n    try {\n      // 拿到接口文件里返回的响应值\n      const response = await handler(event)\n      // 自定义统一的结构\n      // 自定义code，自定义 message\n      return { code: 200, data: response, message: 'ok' }\n    } catch (err) {\n      // 自定义错误响应体\n      return { code: 500, message: 'error' + err }\n      // 或直接抛出错误\n\t  // throw createError({\n      //  statusCode: 500,\n      //  message: '出错啦，请稍后再试～',\n      //})\n    }\n  })\n```\n\n使用时将 `defineEventHandler` 替换为 `defineWrappedResponseHandler` ，同时直接 `return data` 。\n\n```typescript [server/api/demo.ts]\nexport default defineWrappedResponseHandler(async (event) => {\n  const schema = z.object({\n    id: z.string(),\n    user_id: z.string().optional(),\n  })\n  const query = await useSafeValidatedQuery(event, schema)\n\n  if (!query.success) {\n    throw createError({\n      statusCode: 400,\n      statusMessage: (query as any).message ?? '参数错误',\n    })\n  }\n\n  // 伪代码\n  const data =  await prisma.findMany()\n  \n  return data\n})\n```\n\n## 在 plugins 中使用 hook\n\n[官方文档](https://nitro.build/guide/plugins#request-and-response-lifecycle)\n\n`nitro` 中有多个生命周期钩子可供自定义，可自行设置标准响应体，打印日志等\n\n```typescript [server/plugins/response.ts]\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook(\"request\", (event) => {\n    console.log(\"on request\", event.path);\n  });\n\n  nitroApp.hooks.hook(\"beforeResponse\", (event, { body }) => {\n    console.log(\"on response\", event.path, { body });\n  });\n\n  nitroApp.hooks.hook(\"afterResponse\", (event, { body }) => {\n    console.log(\"on after response\", event.path, { body });\n  });\n});\n```\n\n使用 `beforeResponse` 实现自定义响应体\n\n```typescript [server/plugins/response.ts]\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook(\"beforeResponse\", (event, { body }) => {\n    if (t?.body) {\n\t    t.body = {\n\t\t    code: 200,\n\t\t    data: t.body,\n\t\t    message: 'ok'\n\t    }\n    }\n  });\n});\n```\n\n以上就是两种设置统一响应体的方式\n## errorHandler \n\n[官方文档](https://nitro.build/config#errorhandler)\n\n> Path to a custom runtime error handler. Replacing nitro's built-in error page. The error handler is given an `H3Error` and `H3Event`. If the handler returns a promise it is awaited. The handler is expected to send a response of its own. Below is an example where a plain-text response is returned using h3's functions.\n\n`nitro` 中有一个默认的错误页面，你可以自定义自己的 `errorHandler` 用来和统一响应体\n\n```typescript [nuxt.config.ts]\nexport default defineNuxtConfig({\n\tfuture: {\n\t    compatibilityVersion: 4,\n\t},\n\tnitro: {\n\t\terrorHandler: '~~/server/error'\n\t}\n})\n```\n\n\n```typescript [server/error.ts]\nexport default defineNitroErrorHandler((error, event) => {\n  setResponseHeader(event, 'Content-Type', 'application/json')\n  console.log(`[${new Date().toLocaleDateString()}]-[nitro error]: `, error)\n  return send(event, { data: null, message: '服务器异常' })\n})\n```\n\n以上就是群里小伙伴们各自实践中得到的经验！\n\n希望对你有所帮助☺️",{"title":5,"description":1086},"post/nuxt/nitro/standard-response-global-error-handler",[17,1094],"Nitro",[1096,1097],"nuxt@3.17.2","nitro@2.11.12","ZQlojrXISILDPxcm9Vg6BrwTc9O8e0tNbEpNht1081o",[1100,1104],{"title":1101,"path":1102,"stem":1103},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":1105,"path":1106,"stem":1107},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005085663]