[{"data":1,"prerenderedAt":716},["ShallowReactive",2],{"docs-\u002Fdocs\u002Fmain\u002Ffrontend":3},{"id":4,"title":5,"body":6,"description":704,"extension":705,"group":706,"meta":707,"navigation":523,"order":708,"path":709,"section":710,"seo":711,"source":712,"stem":713,"version":714,"__hash__":715},"docs\u002Fdocs\u002Fmain\u002Ffrontend.md","Connect a frontend",{"type":7,"value":8,"toc":696},"minimark",[9,13,18,32,36,81,85,92,256,271,275,282,289,349,352,647,658,662,673,681,685,692],[10,11,12],"p",{},"Gonex handles backend HTTP APIs. Nuxt, Vue, React or Flutter can consume those APIs. They do not import Go packages directly: the browser\u002Fclient communicates with your Go process over HTTP.",[14,15,17],"h2",{"id":16},"start-with-a-working-endpoint","Start with a working endpoint",[10,19,20,21,26,27,31],{},"Complete ",[22,23,25],"a",{"href":24},"\u002Fdocs\u002Fmain\u002Fbuilding-api\u002F","Build a REST API"," and confirm that ",[28,29,30],"code",{},"http:\u002F\u002F127.0.0.1:8080\u002Fapi\u002Fusers"," returns a JSON list after completing the resource API guide. Keep that Gonex process running while developing your frontend.",[14,33,35],{"id":34},"choose-how-the-frontend-is-served","Choose how the frontend is served",[37,38,39,55],"table",{},[40,41,42],"thead",{},[43,44,45,49,52],"tr",{},[46,47,48],"th",{},"Approach",[46,50,51],{},"What serves the page?",[46,53,54],{},"Where Gonex runs",[56,57,58,70],"tbody",{},[43,59,60,64,67],{},[61,62,63],"td",{},"Separate Nuxt\u002FVue\u002FReact frontend",[61,65,66],{},"Its dev server, static host or SSR server",[61,68,69],{},"A Go backend process",[43,71,72,75,78],{},[61,73,74],{},"Static frontend hosted on GitHub Pages",[61,76,77],{},"GitHub Pages",[61,79,80],{},"A separate backend host",[14,82,84],{"id":83},"browser-requests-on-the-same-origin","Browser requests on the same origin",[10,86,87,88,91],{},"If a reverse proxy routes ",[28,89,90],{},"\u002Fapi\u002F"," to Gonex and serves your frontend on the same HTTPS origin, use relative requests:",[93,94,99],"pre",{"className":95,"code":96,"language":97,"meta":98,"style":98},"language-ts shiki shiki-themes light-plus light-plus dark-plus","const response = await fetch('\u002Fapi\u002Fusers')\nif (!response.ok) throw new Error('Could not load users')\nconst data: { items: { id: string; name: string; email: string }[] } = await response.json()\nconsole.log(data.items)\n","ts","",[28,100,101,136,174,234],{"__ignoreMap":98},[102,103,106,110,114,118,122,126,129,133],"span",{"class":104,"line":105},"line",1,[102,107,109],{"class":108},"stxUP","const",[102,111,113],{"class":112},"syj1l"," response",[102,115,117],{"class":116},"s_Yst"," = ",[102,119,121],{"class":120},"smUd-","await",[102,123,125],{"class":124},"swoLU"," fetch",[102,127,128],{"class":116},"(",[102,130,132],{"class":131},"sFD8J","'\u002Fapi\u002Fusers'",[102,134,135],{"class":116},")\n",[102,137,139,142,145,149,152,155,158,161,164,167,169,172],{"class":104,"line":138},2,[102,140,141],{"class":120},"if",[102,143,144],{"class":116}," (!",[102,146,148],{"class":147},"sswaR","response",[102,150,151],{"class":116},".",[102,153,154],{"class":147},"ok",[102,156,157],{"class":116},") ",[102,159,160],{"class":120},"throw",[102,162,163],{"class":108}," new",[102,165,166],{"class":124}," Error",[102,168,128],{"class":116},[102,170,171],{"class":131},"'Could not load users'",[102,173,135],{"class":116},[102,175,177,179,182,185,188,190,193,196,200,203,206,208,210,212,215,217,219,222,224,226,228,231],{"class":104,"line":176},3,[102,178,109],{"class":108},[102,180,181],{"class":112}," data",[102,183,184],{"class":116},": { ",[102,186,187],{"class":147},"items",[102,189,184],{"class":116},[102,191,192],{"class":147},"id",[102,194,195],{"class":116},": ",[102,197,199],{"class":198},"sopvc","string",[102,201,202],{"class":116},"; ",[102,204,205],{"class":147},"name",[102,207,195],{"class":116},[102,209,199],{"class":198},[102,211,202],{"class":116},[102,213,214],{"class":147},"email",[102,216,195],{"class":116},[102,218,199],{"class":198},[102,220,221],{"class":116}," }[] } = ",[102,223,121],{"class":120},[102,225,113],{"class":147},[102,227,151],{"class":116},[102,229,230],{"class":124},"json",[102,232,233],{"class":116},"()\n",[102,235,237,240,242,245,247,250,252,254],{"class":104,"line":236},4,[102,238,239],{"class":147},"console",[102,241,151],{"class":116},[102,243,244],{"class":124},"log",[102,246,128],{"class":116},[102,248,249],{"class":147},"data",[102,251,151],{"class":116},[102,253,187],{"class":147},[102,255,135],{"class":116},[10,257,258,259,262,263,270],{},"Configure the reverse proxy to forward ",[28,260,261],{},"\u002Fapi\u002Fusers"," ",[264,265,266,267],"strong",{},"without stripping ",[28,268,269],{},"\u002Fapi",", because that is the route registered by the resource API example. In development, use your frontend tool's development proxy if you want the same relative URLs.",[14,272,274],{"id":273},"nuxt-example-with-an-explicit-api-origin","Nuxt example with an explicit API origin",[10,276,277,278,281],{},"The following client-only request can be placed inside a Nuxt page's ",[28,279,280],{},"\u003Cscript setup lang=\"ts\">",". This example uses Nuxt's public runtime config so the browser can reach the correct backend URL.",[10,283,284,285,288],{},"In ",[28,286,287],{},"nuxt.config.ts",":",[93,290,292],{"className":95,"code":291,"language":97,"meta":98,"style":98},"export default defineNuxtConfig({\n  runtimeConfig: {\n    public: {\n      apiBase: 'http:\u002F\u002F127.0.0.1:8080'\n    }\n  }\n})\n",[28,293,294,308,316,323,331,337,343],{"__ignoreMap":98},[102,295,296,299,302,305],{"class":104,"line":105},[102,297,298],{"class":120},"export",[102,300,301],{"class":120}," default",[102,303,304],{"class":124}," defineNuxtConfig",[102,306,307],{"class":116},"({\n",[102,309,310,313],{"class":104,"line":138},[102,311,312],{"class":147},"  runtimeConfig:",[102,314,315],{"class":116}," {\n",[102,317,318,321],{"class":104,"line":176},[102,319,320],{"class":147},"    public:",[102,322,315],{"class":116},[102,324,325,328],{"class":104,"line":236},[102,326,327],{"class":147},"      apiBase:",[102,329,330],{"class":131}," 'http:\u002F\u002F127.0.0.1:8080'\n",[102,332,334],{"class":104,"line":333},5,[102,335,336],{"class":116},"    }\n",[102,338,340],{"class":104,"line":339},6,[102,341,342],{"class":116},"  }\n",[102,344,346],{"class":104,"line":345},7,[102,347,348],{"class":116},"})\n",[10,350,351],{},"In your page:",[93,353,357],{"className":354,"code":355,"language":356,"meta":98,"style":98},"language-vue shiki shiki-themes light-plus light-plus dark-plus","\u003Cscript setup lang=\"ts\">\nconst config = useRuntimeConfig()\nconst { data, status, error } = await useFetch\u003C{ items: { id: string; name: string; email: string }[] }>(\n  `${config.public.apiBase}\u002Fapi\u002Fusers`,\n  { server: false }\n)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp v-if=\"status === 'pending'\">Loading…\u003C\u002Fp>\n  \u003Cp v-else-if=\"error\">Could not load users.\u003C\u002Fp>\n  \u003Cul v-else>\u003Cli v-for=\"user in data?.items\" :key=\"user.id\">{{ user.name }}\u003C\u002Fli>\u003C\u002Ful>\n\u003C\u002Ftemplate>\n","vue",[28,358,359,386,400,460,491,505,509,518,525,535,563,589,638],{"__ignoreMap":98},[102,360,361,365,369,373,376,379,383],{"class":104,"line":105},[102,362,364],{"class":363},"s9Xrk","\u003C",[102,366,368],{"class":367},"swrg_","script",[102,370,372],{"class":371},"ssBXb"," setup",[102,374,375],{"class":371}," lang",[102,377,378],{"class":116},"=",[102,380,382],{"class":381},"swG-L","\"ts\"",[102,384,385],{"class":363},">\n",[102,387,388,390,393,395,398],{"class":104,"line":138},[102,389,109],{"class":108},[102,391,392],{"class":112}," config",[102,394,117],{"class":116},[102,396,397],{"class":124},"useRuntimeConfig",[102,399,233],{"class":116},[102,401,402,404,407,409,412,415,417,420,423,425,428,431,433,435,437,439,441,443,445,447,449,451,453,455,457],{"class":104,"line":176},[102,403,109],{"class":108},[102,405,406],{"class":116}," { ",[102,408,249],{"class":112},[102,410,411],{"class":116},", ",[102,413,414],{"class":112},"status",[102,416,411],{"class":116},[102,418,419],{"class":112},"error",[102,421,422],{"class":116}," } = ",[102,424,121],{"class":120},[102,426,427],{"class":124}," useFetch",[102,429,430],{"class":116},"\u003C{ ",[102,432,187],{"class":147},[102,434,184],{"class":116},[102,436,192],{"class":147},[102,438,195],{"class":116},[102,440,199],{"class":198},[102,442,202],{"class":116},[102,444,205],{"class":147},[102,446,195],{"class":116},[102,448,199],{"class":198},[102,450,202],{"class":116},[102,452,214],{"class":147},[102,454,195],{"class":116},[102,456,199],{"class":198},[102,458,459],{"class":116}," }[] }>(\n",[102,461,462,465,468,471,474,477,479,482,485,488],{"class":104,"line":236},[102,463,464],{"class":131},"  `",[102,466,467],{"class":108},"${",[102,469,470],{"class":147},"config",[102,472,151],{"class":473},"sw10w",[102,475,476],{"class":147},"public",[102,478,151],{"class":473},[102,480,481],{"class":147},"apiBase",[102,483,484],{"class":108},"}",[102,486,487],{"class":131},"\u002Fapi\u002Fusers`",[102,489,490],{"class":116},",\n",[102,492,493,496,499,502],{"class":104,"line":333},[102,494,495],{"class":116},"  { ",[102,497,498],{"class":147},"server:",[102,500,501],{"class":108}," false",[102,503,504],{"class":116}," }\n",[102,506,507],{"class":104,"line":339},[102,508,135],{"class":116},[102,510,511,514,516],{"class":104,"line":345},[102,512,513],{"class":363},"\u003C\u002F",[102,515,368],{"class":367},[102,517,385],{"class":363},[102,519,521],{"class":104,"line":520},8,[102,522,524],{"emptyLinePlaceholder":523},true,"\n",[102,526,528,530,533],{"class":104,"line":527},9,[102,529,364],{"class":363},[102,531,532],{"class":367},"template",[102,534,385],{"class":363},[102,536,538,541,543,546,548,551,554,557,559,561],{"class":104,"line":537},10,[102,539,540],{"class":363},"  \u003C",[102,542,10],{"class":367},[102,544,545],{"class":371}," v-if",[102,547,378],{"class":116},[102,549,550],{"class":381},"\"status === 'pending'\"",[102,552,553],{"class":363},">",[102,555,556],{"class":116},"Loading…",[102,558,513],{"class":363},[102,560,10],{"class":367},[102,562,385],{"class":363},[102,564,566,568,570,573,575,578,580,583,585,587],{"class":104,"line":565},11,[102,567,540],{"class":363},[102,569,10],{"class":367},[102,571,572],{"class":371}," v-else-if",[102,574,378],{"class":116},[102,576,577],{"class":381},"\"error\"",[102,579,553],{"class":363},[102,581,582],{"class":116},"Could not load users.",[102,584,513],{"class":363},[102,586,10],{"class":367},[102,588,385],{"class":363},[102,590,592,594,597,600,603,606,609,611,614,617,619,622,624,627,629,631,634,636],{"class":104,"line":591},12,[102,593,540],{"class":363},[102,595,596],{"class":367},"ul",[102,598,599],{"class":371}," v-else",[102,601,602],{"class":363},">\u003C",[102,604,605],{"class":367},"li",[102,607,608],{"class":371}," v-for",[102,610,378],{"class":116},[102,612,613],{"class":381},"\"user in data?.items\"",[102,615,616],{"class":371}," :key",[102,618,378],{"class":116},[102,620,621],{"class":381},"\"user.id\"",[102,623,553],{"class":363},[102,625,626],{"class":116},"{{ user.name }}",[102,628,513],{"class":363},[102,630,605],{"class":367},[102,632,633],{"class":363},">\u003C\u002F",[102,635,596],{"class":367},[102,637,385],{"class":363},[102,639,641,643,645],{"class":104,"line":640},13,[102,642,513],{"class":363},[102,644,532],{"class":367},[102,646,385],{"class":363},[10,648,649,650,653,654,657],{},"Set ",[28,651,652],{},"NUXT_PUBLIC_API_BASE"," to your backend's public HTTPS origin in production. For a statically generated frontend, configure it at build time and rebuild when it changes. An end user's browser cannot use your development machine's ",[28,655,656],{},"127.0.0.1"," as a production backend.",[14,659,661],{"id":660},"different-origins-require-a-cors-policy","Different origins require a CORS policy",[10,663,664,665,668,669,672],{},"A frontend at ",[28,666,667],{},"http:\u002F\u002Flocalhost:3000"," and a backend at ",[28,670,671],{},"http:\u002F\u002F127.0.0.1:8080"," are different origins. The browser blocks cross-origin calls unless the backend explicitly permits the frontend origin. A development proxy avoids this browser cross-origin boundary; a separately hosted frontend needs a deliberate CORS policy.",[10,674,675,676,680],{},"The example API does not enable CORS automatically. Configure it according to the ",[22,677,679],{"href":678},"\u002Fdocs\u002Fmain\u002Fsecurity\u002F","security guide",". CORS is not authentication. Avoid broad origin\u002Fcredential policies; keep secrets on the backend. If using cookie-authenticated cross-site requests, review cookie and CSRF behavior as part of your application security design.",[14,682,684],{"id":683},"static-hosting-is-only-the-frontend","Static hosting is only the frontend",[10,686,687,688,151],{},"GitHub Pages can host a generated frontend, but cannot run your Gonex server or database. Deploy the API to a host that runs Go services, then configure the frontend's API URL and allowed origin. See ",[22,689,691],{"href":690},"\u002Fdocs\u002Fmain\u002Fdeployment\u002F","deployment",[693,694,695],"style",{},"html pre.shiki code .stxUP, html code.shiki .stxUP{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#569CD6}html pre.shiki code .syj1l, html code.shiki .syj1l{--shiki-light:#0070C1;--shiki-default:#0070C1;--shiki-dark:#4FC1FF}html pre.shiki code .s_Yst, html code.shiki .s_Yst{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#D4D4D4}html pre.shiki code .smUd-, html code.shiki .smUd-{--shiki-light:#AF00DB;--shiki-default:#AF00DB;--shiki-dark:#C586C0}html pre.shiki code .swoLU, html code.shiki .swoLU{--shiki-light:#795E26;--shiki-default:#795E26;--shiki-dark:#DCDCAA}html pre.shiki code .sFD8J, html code.shiki .sFD8J{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#CE9178}html pre.shiki code .sswaR, html code.shiki .sswaR{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#9CDCFE}html pre.shiki code .sopvc, html code.shiki .sopvc{--shiki-light:#267F99;--shiki-default:#267F99;--shiki-dark:#4EC9B0}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 .s9Xrk, html code.shiki .s9Xrk{--shiki-light:#800000;--shiki-default:#800000;--shiki-dark:#808080}html pre.shiki code .swrg_, html code.shiki .swrg_{--shiki-light:#800000;--shiki-default:#800000;--shiki-dark:#569CD6}html pre.shiki code .ssBXb, html code.shiki .ssBXb{--shiki-light:#E50000;--shiki-default:#E50000;--shiki-dark:#9CDCFE}html pre.shiki code .swG-L, html code.shiki .swG-L{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#CE9178}html pre.shiki code .sw10w, html code.shiki .sw10w{--shiki-light:#000000FF;--shiki-default:#000000FF;--shiki-dark:#D4D4D4}",{"title":98,"searchDepth":138,"depth":138,"links":697},[698,699,700,701,702,703],{"id":16,"depth":138,"text":17},{"id":34,"depth":138,"text":35},{"id":83,"depth":138,"text":84},{"id":273,"depth":138,"text":274},{"id":660,"depth":138,"text":661},{"id":683,"depth":138,"text":684},"Use Gonex as the backend for a Nuxt, Vue, React or Flutter frontend, with clear API URLs and deployment boundaries.","md","APPLICATION DESIGN",{},40,"\u002Fdocs\u002Fmain\u002Ffrontend","build",{"title":5,"description":704},null,"docs\u002Fmain\u002Ffrontend","main","JOf3m0kQw3Hjh5OISJSwTqmgxPu4GLR4QMtKuGM7Vu0",1792521640427]