[{"data":1,"prerenderedAt":761},["ShallowReactive",2],{"nav-stories":3,"footer-stories":61,"blog-integrating-fastapi-nuxt":74},[4,16,25,34,43,52],{"id":5,"color":6,"extension":7,"image":8,"label":9,"link":10,"meta":11,"order":12,"stem":13,"text":14,"__hash__":15},"stories\u002Fstories\u002F01-data-center.yml",null,"yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1558494949-ef010cbdcc31?w=1080","DATA_CENTER","https:\u002F\u002Fx.com\u002Fabbeytetteh_",{},1,"stories\u002F01-data-center","Racking new servers. 40gbit backbone online.","0QUZQbaANhdO8WemZxkDdO7vbVopfnynHtH9FxBZb_w",{"id":17,"color":6,"extension":7,"image":18,"label":19,"link":6,"meta":20,"order":21,"stem":22,"text":23,"__hash__":24},"stories\u002Fstories\u002F02-thoughts.yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1498050108023-c5249f4df085?w=1080","THOUGHTS",{},2,"stories\u002F02-thoughts","Late night bug hunting. Found the memory leak.","Gd1am954aasY6HRHD7hCtOuessXb6zYZ8iizS501ICg",{"id":26,"color":27,"extension":7,"image":6,"label":28,"link":6,"meta":29,"order":30,"stem":31,"text":32,"__hash__":33},"stories\u002Fstories\u002F03-coding.yml","#3b82f6","CODING",{},3,"stories\u002F03-coding","Just thinking about how much easier life is with Swarm.","vLAyiGUPtlXB2SHa5KM_U2AaK4QkG3Og85UEUE7qzgM",{"id":35,"color":6,"extension":7,"image":36,"label":37,"link":6,"meta":38,"order":39,"stem":40,"text":41,"__hash__":42},"stories\u002Fstories\u002F04-update.yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1591799264318-7e6ef8ddb7ea?w=1080","UPDATE",{},4,"stories\u002F04-update","New cluster nodes arrived. Prepping for installation.","kyT60N5C6Re_jMonZbgNy0PbQhzXmUWxDbD0D_v43ts",{"id":44,"color":45,"extension":7,"image":6,"label":46,"link":6,"meta":47,"order":48,"stem":49,"text":50,"__hash__":51},"stories\u002Fstories\u002F05-setup.yml","#86868b","SETUP",{},5,"stories\u002F05-setup","Optimizing the telemetry pipeline for 1M req\u002Fs.","cPOBkzoyXsCmPgRO2d80Hj3vm4MP-6nAejtlQ5iuSzw",{"id":53,"color":6,"extension":7,"image":54,"label":55,"link":6,"meta":56,"order":57,"stem":58,"text":59,"__hash__":60},"stories\u002Fstories\u002F06-travel.yml","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1560969184-10fe8719e047?w=1080","TRAVEL",{},6,"stories\u002F06-travel","Travel log — system architecture workshop in Berlin.","jnOxerdF6usAIHdR35Z-opx0LJAy9kZluXnZhtz62Z0",[62,64,66,68,70,72],{"id":5,"color":6,"extension":7,"image":8,"label":9,"link":10,"meta":63,"order":12,"stem":13,"text":14,"__hash__":15},{},{"id":17,"color":6,"extension":7,"image":18,"label":19,"link":6,"meta":65,"order":21,"stem":22,"text":23,"__hash__":24},{},{"id":26,"color":27,"extension":7,"image":6,"label":28,"link":6,"meta":67,"order":30,"stem":31,"text":32,"__hash__":33},{},{"id":35,"color":6,"extension":7,"image":36,"label":37,"link":6,"meta":69,"order":39,"stem":40,"text":41,"__hash__":42},{},{"id":44,"color":45,"extension":7,"image":6,"label":46,"link":6,"meta":71,"order":48,"stem":49,"text":50,"__hash__":51},{},{"id":53,"color":6,"extension":7,"image":54,"label":55,"link":6,"meta":73,"order":57,"stem":58,"text":59,"__hash__":60},{},{"id":75,"title":76,"body":77,"date":745,"description":746,"extension":747,"meta":748,"navigation":136,"path":749,"readTime":750,"seo":751,"stem":752,"tags":753,"thumbnail":759,"__hash__":760},"blog\u002Fblog\u002Fintegrating-fastapi-nuxt.md","Integrating FastAPI with Next-Gen Vue\u002FNuxt Interfaces",{"type":78,"value":79,"toc":739},"minimark",[80,84,89,92,304,311,315,326,354,357,634,637,641,644,725,728,732,735],[81,82,83],"p",{},"Most portfolio sites gloss over the API boundary — the messy strip of no-man's-land between a Python backend and a JavaScript frontend. In production, that seam is where bugs live. This post is about closing it properly.",[85,86,88],"h2",{"id":87},"why-fastapi","Why FastAPI",[81,90,91],{},"FastAPI is the right tool for this job for two reasons: it generates an OpenAPI spec out of the box, and its Pydantic models give you runtime validation for free. The spec becomes your single source of truth for the interface contract.",[93,94,99],"pre",{"className":95,"code":96,"language":97,"meta":98,"style":98},"language-python shiki shiki-themes vitesse-light","from fastapi import FastAPI\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\nclass DeploymentPayload(BaseModel):\n    service_name: str\n    image_tag: str\n    replicas: int = 1\n\n@app.post(\"\u002Fdeployments\", response_model=DeploymentPayload)\nasync def create_deployment(payload: DeploymentPayload):\n    # schedule the deployment...\n    return payload\n","python","",[100,101,102,120,132,138,153,157,177,190,200,218,223,265,288,295],"code",{"__ignoreMap":98},[103,104,106,110,114,117],"span",{"class":105,"line":12},"line",[103,107,109],{"class":108},"sbBg2","from",[103,111,113],{"class":112},"suHK_"," fastapi ",[103,115,116],{"class":108},"import",[103,118,119],{"class":112}," FastAPI\n",[103,121,122,124,127,129],{"class":105,"line":21},[103,123,109],{"class":108},[103,125,126],{"class":112}," pydantic ",[103,128,116],{"class":108},[103,130,131],{"class":112}," BaseModel\n",[103,133,134],{"class":105,"line":30},[103,135,137],{"emptyLinePlaceholder":136},true,"\n",[103,139,140,143,147,150],{"class":105,"line":39},[103,141,142],{"class":112},"app ",[103,144,146],{"class":145},"sYZai","=",[103,148,149],{"class":112}," FastAPI",[103,151,152],{"class":145},"()\n",[103,154,155],{"class":105,"line":48},[103,156,137],{"emptyLinePlaceholder":136},[103,158,159,163,167,170,174],{"class":105,"line":57},[103,160,162],{"class":161},"si04Y","class",[103,164,166],{"class":165},"sUxyF"," DeploymentPayload",[103,168,169],{"class":145},"(",[103,171,173],{"class":172},"sySUi","BaseModel",[103,175,176],{"class":145},"):\n",[103,178,180,183,186],{"class":105,"line":179},7,[103,181,182],{"class":112},"    service_name",[103,184,185],{"class":145},":",[103,187,189],{"class":188},"su6XF"," str\n",[103,191,193,196,198],{"class":105,"line":192},8,[103,194,195],{"class":112},"    image_tag",[103,197,185],{"class":145},[103,199,189],{"class":188},[103,201,203,206,208,211,214],{"class":105,"line":202},9,[103,204,205],{"class":112},"    replicas",[103,207,185],{"class":145},[103,209,210],{"class":188}," int",[103,212,213],{"class":145}," =",[103,215,217],{"class":216},"s-TwI"," 1\n",[103,219,221],{"class":105,"line":220},10,[103,222,137],{"emptyLinePlaceholder":136},[103,224,226,229,232,235,238,240,244,248,250,253,257,259,262],{"class":105,"line":225},11,[103,227,228],{"class":145},"@",[103,230,231],{"class":172},"app",[103,233,234],{"class":145},".",[103,236,237],{"class":172},"post",[103,239,169],{"class":145},[103,241,243],{"class":242},"sSP4y","\"",[103,245,247],{"class":246},"spphp","\u002Fdeployments",[103,249,243],{"class":242},[103,251,252],{"class":145},",",[103,254,256],{"class":255},"svycV"," response_model",[103,258,146],{"class":145},[103,260,261],{"class":112},"DeploymentPayload",[103,263,264],{"class":145},")\n",[103,266,268,271,274,277,279,282,284,286],{"class":105,"line":267},12,[103,269,270],{"class":161},"async",[103,272,273],{"class":161}," def",[103,275,276],{"class":172}," create_deployment",[103,278,169],{"class":145},[103,280,281],{"class":112},"payload",[103,283,185],{"class":145},[103,285,166],{"class":112},[103,287,176],{"class":145},[103,289,291],{"class":105,"line":290},13,[103,292,294],{"class":293},"s8zF2","    # schedule the deployment...\n",[103,296,298,301],{"class":105,"line":297},14,[103,299,300],{"class":108},"    return",[103,302,303],{"class":112}," payload\n",[81,305,306,307,310],{},"The ",[100,308,309],{},"response_model"," annotation is the key — FastAPI will serialize exactly what you declare, and nothing more. No accidental data leaks.",[85,312,314],{"id":313},"type-generation-on-the-nuxt-side","Type Generation on the Nuxt Side",[81,316,317,318,321,322,325],{},"Once the OpenAPI spec exists at ",[100,319,320],{},"http:\u002F\u002Flocalhost:8000\u002Fopenapi.json",", use ",[100,323,324],{},"openapi-typescript"," to generate TypeScript types:",[93,327,331],{"className":328,"code":329,"language":330,"meta":98,"style":98},"language-bash shiki shiki-themes vitesse-light","$ npx openapi-typescript http:\u002F\u002Flocalhost:8000\u002Fopenapi.json -o src\u002Ftypes\u002Fapi.ts\n","bash",[100,332,333],{"__ignoreMap":98},[103,334,335,338,341,344,347,351],{"class":105,"line":12},[103,336,337],{"class":172},"$",[103,339,340],{"class":246}," npx",[103,342,343],{"class":246}," openapi-typescript",[103,345,346],{"class":246}," http:\u002F\u002Flocalhost:8000\u002Fopenapi.json",[103,348,350],{"class":349},"sEi1f"," -o",[103,352,353],{"class":246}," src\u002Ftypes\u002Fapi.ts\n",[81,355,356],{},"Now your Nuxt composable can be fully typed with zero manual interface definitions:",[93,358,362],{"className":359,"code":360,"language":361,"meta":98,"style":98},"language-typescript shiki shiki-themes vitesse-light","import type { components } from '~\u002Ftypes\u002Fapi'\n\ntype Deployment = components['schemas']['DeploymentPayload']\n\nexport const useDeployments = () => {\n  const list = ref\u003CDeployment[]>([])\n\n  const create = async (payload: Deployment) => {\n    const data = await $fetch\u003CDeployment>('\u002Fapi\u002Fdeployments', {\n      method: 'POST',\n      body: payload,\n    })\n    list.value.push(data)\n  }\n\n  return { list, create }\n}\n","typescript",[100,363,364,392,396,431,435,457,479,483,511,545,562,573,578,599,604,609,628],{"__ignoreMap":98},[103,365,366,368,371,374,377,380,383,386,389],{"class":105,"line":12},[103,367,116],{"class":108},[103,369,370],{"class":108}," type",[103,372,373],{"class":145}," {",[103,375,376],{"class":255}," components",[103,378,379],{"class":145}," }",[103,381,382],{"class":108}," from",[103,384,385],{"class":242}," '",[103,387,388],{"class":246},"~\u002Ftypes\u002Fapi",[103,390,391],{"class":242},"'\n",[103,393,394],{"class":105,"line":21},[103,395,137],{"emptyLinePlaceholder":136},[103,397,398,401,404,406,408,411,414,417,419,422,424,426,428],{"class":105,"line":30},[103,399,400],{"class":161},"type",[103,402,403],{"class":165}," Deployment",[103,405,213],{"class":145},[103,407,376],{"class":165},[103,409,410],{"class":145},"[",[103,412,413],{"class":242},"'",[103,415,416],{"class":246},"schemas",[103,418,413],{"class":242},[103,420,421],{"class":145},"][",[103,423,413],{"class":242},[103,425,261],{"class":246},[103,427,413],{"class":242},[103,429,430],{"class":145},"]\n",[103,432,433],{"class":105,"line":39},[103,434,137],{"emptyLinePlaceholder":136},[103,436,437,440,443,446,448,451,454],{"class":105,"line":48},[103,438,439],{"class":108},"export",[103,441,442],{"class":161}," const ",[103,444,445],{"class":172},"useDeployments",[103,447,213],{"class":145},[103,449,450],{"class":145}," ()",[103,452,453],{"class":145}," =>",[103,455,456],{"class":145}," {\n",[103,458,459,462,465,467,470,473,476],{"class":105,"line":57},[103,460,461],{"class":161},"  const ",[103,463,464],{"class":255},"list",[103,466,213],{"class":145},[103,468,469],{"class":172}," ref",[103,471,472],{"class":145},"\u003C",[103,474,475],{"class":165},"Deployment",[103,477,478],{"class":145},"[]>([])\n",[103,480,481],{"class":105,"line":179},[103,482,137],{"emptyLinePlaceholder":136},[103,484,485,487,490,492,495,497,499,502,504,507,509],{"class":105,"line":192},[103,486,461],{"class":161},[103,488,489],{"class":172},"create",[103,491,213],{"class":145},[103,493,494],{"class":161}," async ",[103,496,169],{"class":145},[103,498,281],{"class":255},[103,500,501],{"class":145},": ",[103,503,475],{"class":165},[103,505,506],{"class":145},")",[103,508,453],{"class":145},[103,510,456],{"class":145},[103,512,513,516,519,521,524,527,529,531,534,536,539,541,543],{"class":105,"line":202},[103,514,515],{"class":161},"    const ",[103,517,518],{"class":255},"data",[103,520,213],{"class":145},[103,522,523],{"class":108}," await",[103,525,526],{"class":172}," $fetch",[103,528,472],{"class":145},[103,530,475],{"class":165},[103,532,533],{"class":145},">(",[103,535,413],{"class":242},[103,537,538],{"class":246},"\u002Fapi\u002Fdeployments",[103,540,413],{"class":242},[103,542,252],{"class":145},[103,544,456],{"class":145},[103,546,547,550,552,554,557,559],{"class":105,"line":220},[103,548,549],{"class":188},"      method",[103,551,501],{"class":145},[103,553,413],{"class":242},[103,555,556],{"class":246},"POST",[103,558,413],{"class":242},[103,560,561],{"class":145},",\n",[103,563,564,567,569,571],{"class":105,"line":225},[103,565,566],{"class":188},"      body",[103,568,501],{"class":145},[103,570,281],{"class":255},[103,572,561],{"class":145},[103,574,575],{"class":105,"line":267},[103,576,577],{"class":145},"    })\n",[103,579,580,583,585,588,590,593,595,597],{"class":105,"line":290},[103,581,582],{"class":255},"    list",[103,584,234],{"class":145},[103,586,587],{"class":255},"value",[103,589,234],{"class":145},[103,591,592],{"class":172},"push",[103,594,169],{"class":145},[103,596,518],{"class":255},[103,598,264],{"class":145},[103,600,601],{"class":105,"line":297},[103,602,603],{"class":145},"  }\n",[103,605,607],{"class":105,"line":606},15,[103,608,137],{"emptyLinePlaceholder":136},[103,610,612,615,618,620,623,625],{"class":105,"line":611},16,[103,613,614],{"class":108},"  return",[103,616,617],{"class":145}," { ",[103,619,464],{"class":255},[103,621,622],{"class":145},", ",[103,624,489],{"class":255},[103,626,627],{"class":145}," }\n",[103,629,631],{"class":105,"line":630},17,[103,632,633],{"class":145},"}\n",[81,635,636],{},"If the backend changes a field name, TypeScript will scream at you at compile time rather than letting a runtime mismatch slip to production.",[85,638,640],{"id":639},"cors-and-the-proxy-pattern","CORS and the Proxy Pattern",[81,642,643],{},"Never expose your Python backend directly to the browser in production. Run Nuxt's built-in server as a reverse proxy instead:",[93,645,647],{"className":359,"code":646,"language":361,"meta":98,"style":98},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  routeRules: {\n    '\u002Fapi\u002F**': {\n      proxy: { to: 'http:\u002F\u002Ffastapi-service:8000\u002F**' },\n    },\n  },\n})\n",[100,648,649,654,667,675,687,710,715,720],{"__ignoreMap":98},[103,650,651],{"class":105,"line":12},[103,652,653],{"class":293},"\u002F\u002F nuxt.config.ts\n",[103,655,656,658,661,664],{"class":105,"line":21},[103,657,439],{"class":108},[103,659,660],{"class":108}," default",[103,662,663],{"class":172}," defineNuxtConfig",[103,665,666],{"class":145},"({\n",[103,668,669,672],{"class":105,"line":30},[103,670,671],{"class":188},"  routeRules",[103,673,674],{"class":145},": {\n",[103,676,677,680,683,685],{"class":105,"line":39},[103,678,679],{"class":242},"    '",[103,681,682],{"class":246},"\u002Fapi\u002F**",[103,684,413],{"class":242},[103,686,674],{"class":145},[103,688,689,692,695,698,700,702,705,707],{"class":105,"line":48},[103,690,691],{"class":188},"      proxy",[103,693,694],{"class":145},": { ",[103,696,697],{"class":188},"to",[103,699,501],{"class":145},[103,701,413],{"class":242},[103,703,704],{"class":246},"http:\u002F\u002Ffastapi-service:8000\u002F**",[103,706,413],{"class":242},[103,708,709],{"class":145}," },\n",[103,711,712],{"class":105,"line":57},[103,713,714],{"class":145},"    },\n",[103,716,717],{"class":105,"line":179},[103,718,719],{"class":145},"  },\n",[103,721,722],{"class":105,"line":192},[103,723,724],{"class":145},"})\n",[81,726,727],{},"This collapses the cross-origin problem entirely — the browser sees one origin, and your FastAPI service never needs a CORS header.",[85,729,731],{"id":730},"whats-next","What's Next",[81,733,734],{},"The next evolution here is adding a message queue between the two services. For long-running tasks (deployments, report generation), returning a job ID and polling via a Nuxt server-sent-event endpoint beats blocking HTTP every time.",[736,737,738],"style",{},"html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}html pre.shiki code .sEi1f, html code.shiki .sEi1f{--shiki-default:#A65E2B}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 .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}html pre.shiki code .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}html pre.shiki code .su6XF, html code.shiki .su6XF{--shiki-default:#998418}html pre.shiki code .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}",{"title":98,"searchDepth":21,"depth":21,"links":740},[741,742,743,744],{"id":87,"depth":21,"text":88},{"id":313,"depth":21,"text":314},{"id":639,"depth":21,"text":640},{"id":730,"depth":21,"text":731},"2026-04-28","How to architect scalable, strongly typed API layers between Python backends and modern JavaScript frontends.","md",{},"\u002Fblog\u002Fintegrating-fastapi-nuxt","7 min",{"title":76,"description":746},"blog\u002Fintegrating-fastapi-nuxt",[754,755,756,757,758],"FastAPI","Nuxt","TypeScript","Python","API","\u002Fimages\u002Fthumbnails\u002Fintegrating-fastapi-nuxt.png","oeUJcbHal09PEvokXEL12iWhI43re_Pner_ID9zxtMc",1779361989410]