[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"navigation":3,"url-settings":86,"blog-\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger":671,"blog-author-\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger":1330,"i-material-symbols:arrow-back-rounded":1342},{"id":4,"extension":5,"footer":6,"header":73,"meta":83,"stem":84,"__hash__":85},"navigation\u002Fdata\u002Fshared\u002Fnavigation.yml","yml",{"brand":7,"columns":13,"legal":63},{"name":8,"tagline":9,"downloadCta":10},"Pieces","The memory layer for modern work.",{"label":11,"href":12},"All downloads","\u002Fdownloads",[14,26,39,51],{"title":15,"links":16},"Product",[17,20,23],{"label":18,"href":19},"Pieces Desktop","\u002F",{"label":21,"href":22},"Pieces Enterprise","\u002Fenterprise",{"label":24,"href":25},"Pieces MCP","\u002Fmcp",{"title":27,"links":28},"Resources",[29,33,36],{"label":30,"href":31,"external":32},"Documentation","url:docs.home",true,{"label":34,"href":35},"Blog","\u002Fblog",{"label":37,"href":38,"external":32},"GitHub","url:github.org",{"title":40,"links":41},"Community",[42,45,48],{"label":43,"href":44,"external":32},"Discord","url:social.discord",{"label":46,"href":47,"external":32},"X \u002F Twitter","url:social.x",{"label":49,"href":50,"external":32},"LinkedIn","url:social.linkedin",{"title":52,"links":53},"Company",[54,57,60],{"label":55,"href":56},"About","\u002Fabout",{"label":58,"href":59},"Updates","\u002Fupdates",{"label":61,"href":62},"Contact","\u002Fcontact",[64,67,70],{"label":65,"href":66},"Privacy Policy","\u002Flegal\u002Fprivacy",{"label":68,"href":69},"Refund Policy","\u002Flegal\u002Frefund",{"label":71,"href":72},"Terms of Service","\u002Flegal\u002Fterms",{"links":74,"signIn":75,"contact":78,"cta":80},[],{"label":76,"href":77},"Manage account","url:portal.home",{"label":79,"href":62},"Get in touch",{"label":81,"href":82},"Download","url:routes.downloads",{},"data\u002Fshared\u002Fnavigation","XlSALC7vYCXHcmcXnjJViwIZKsqnQWF4bn4o9RrcSBE",{"id":87,"extension":5,"links":88,"meta":668,"stem":669,"__hash__":670},"urlSettings\u002Fdata\u002Fshared\u002Furls.yml",[89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,169,173,177,181,185,189,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,303,307,311,314,318,322,326,330,334,338,342,346,350,354,358,362,366,370,374,378,382,386,390,394,398,402,406,410,414,418,422,426,430,434,438,442,446,450,454,458,461,465,469,473,477,481,485,488,491,494,497,501,505,508,512,516,520,524,528,532,536,540,544,548,552,556,559,563,567,571,575,579,582,585,588,592,595,599,603,607,611,614,618,622,625,628,631,635,639,643,646,649,652,656,660,664],{"key":90,"label":91,"href":92},"downloads.desktop","Desktop download page","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdownload",{"key":94,"label":95,"href":96},"downloads.macOS.dmgArm64","macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg-arm64\u002Fdownload",{"key":98,"label":99,"href":100},"downloads.macOS.dmgIntel","macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg\u002Fdownload",{"key":102,"label":103,"href":104},"downloads.macOS.pkg","macOS PKG","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg\u002Fdownload",{"key":106,"label":107,"href":108},"downloads.macOS.universal","Desktop macOS Universal","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fmacos-universal\u002Fdownload",{"key":110,"label":111,"href":112},"downloads.macOS.pkgArm64","Desktop macOS PKG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pfd-arm64\u002Fdownload",{"key":114,"label":115,"href":116},"downloads.macOS.pkgIntel","Desktop macOS PKG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pfd\u002Fdownload",{"key":118,"label":119,"href":120},"downloads.windows.appinstaller","Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fpieces_for_x.appinstaller",{"key":122,"label":123,"href":124},"downloads.windows.exe","Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-exe\u002Fdownload",{"key":126,"label":127,"href":128},"downloads.windows.msix","Windows MSIX","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-msix\u002Fdownload",{"key":130,"label":131,"href":132},"downloads.windows.suiteManager","Windows Suite Manager","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_suite_windows\u002Fappinstaller\u002Fdownload",{"key":134,"label":135,"href":136},"downloads.linux.flatpakRepo","Linux Flatpak repository","https:\u002F\u002Fbuilds.pieces.app\u002Fpieces-flatpak-repo\u002Fpieces-flatpak.flatpakrepo",{"key":138,"label":139,"href":140},"downloads.linux.snapDesktop","Linux Snap Desktop","https:\u002F\u002Fsnapcraft.io\u002Fpieces-for-developers",{"key":142,"label":143,"href":144},"downloads.linux.snapPiecesOS","Linux Snap PiecesOS","https:\u002F\u002Fsnapcraft.io\u002Fpieces-os",{"key":146,"label":147,"href":148},"downloads.linux.snap","Desktop Linux Snap package","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fsnap\u002Fdownload",{"key":150,"label":151,"href":152},"downloads.piecesOS.macOS.dmgArm64","PiecesOS macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg-arm64\u002Fdownload",{"key":154,"label":155,"href":156},"downloads.piecesOS.macOS.dmgIntel","PiecesOS macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg\u002Fdownload",{"key":158,"label":159,"href":160},"downloads.piecesOS.macOS.universal","PiecesOS macOS Universal","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fmacos-universal\u002Fdownload",{"key":162,"label":163,"href":164},"downloads.piecesOS.macOS.pkgArm64","PiecesOS macOS PKG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pos-launch-only-arm64\u002Fdownload",{"key":166,"label":167,"href":168},"downloads.piecesOS.macOS.pkgIntel","PiecesOS macOS PKG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pos-launch-only\u002Fdownload",{"key":170,"label":171,"href":172},"downloads.piecesOS.windows.appinstaller","PiecesOS Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fos_server.appinstaller",{"key":174,"label":175,"href":176},"downloads.piecesOS.windows.exe","PiecesOS Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-exe\u002Fdownload",{"key":178,"label":179,"href":180},"downloads.piecesOS.windows.msix","PiecesOS Windows MSIX","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-msix\u002Fdownload",{"key":182,"label":183,"href":184},"downloads.piecesOS.linux.snap","PiecesOS Linux Snap package","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fsnap\u002Fdownload",{"key":186,"label":187,"href":188},"downloads.combined.macOS.appleSilicon","Combined macOS installer Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-arm64\u002Fdownload",{"key":190,"label":191,"href":104},"downloads.combined.macOS.intel","Combined macOS installer Intel",{"key":193,"label":194,"href":195},"downloads.guides.macOS","macOS installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fmacos-installation-guide",{"key":197,"label":198,"href":199},"downloads.guides.windows","Windows installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fwindows-installation-guide",{"key":201,"label":202,"href":203},"downloads.guides.linux","Linux installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide",{"key":205,"label":206,"href":207},"downloads.guides.linuxFlatpak","Linux Flatpak installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide\u002Fflatpak",{"key":209,"label":210,"href":211},"downloads.guides.linuxSnap","Linux Snap installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide\u002Fsnap",{"key":213,"label":214,"href":215},"downloads.guides.piecesOS","PiecesOS manual installation","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fmanual-installation",{"key":217,"label":218,"href":219},"extensions.chrome","Chrome extension","https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Fpieces-save-code-snippets\u002Figbgibhbfonhmjlechmeefimncpekepm",{"key":221,"label":222,"href":223},"extensions.firefox","Firefox add-on","https:\u002F\u002Faddons.mozilla.org\u002Fen-US\u002Ffirefox\u002Faddon\u002Fpieces-save-code-from-the-web\u002F",{"key":225,"label":226,"href":227},"extensions.edge","Edge add-on","https:\u002F\u002Fmicrosoftedge.microsoft.com\u002Faddons\u002Fdetail\u002Fpieces-save-code-snippet\u002Fhglfimcdgonaeeobjckfdabcldfidmim",{"key":229,"label":230,"href":231},"extensions.vscode","VS Code extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.pieces-vscode",{"key":233,"label":234,"href":235},"extensions.visualStudio","Visual Studio extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.PiecesVisualStudio",{"key":237,"label":238,"href":239},"extensions.jetbrains","JetBrains plugin","https:\u002F\u002Fplugins.jetbrains.com\u002Fplugin\u002F17328-pieces--save-search-share--reuse-code-snippets",{"key":241,"label":242,"href":243},"extensions.obsidian","Obsidian plugin","https:\u002F\u002Fobsidian.md\u002Fplugins?id=pieces-for-developers",{"key":245,"label":246,"href":247},"extensions.sublime","Sublime package","https:\u002F\u002Fpackagecontrol.io\u002Fpackages\u002FPieces",{"key":249,"label":250,"href":251},"extensions.neovim","Neovim plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neo_vim",{"key":253,"label":254,"href":255},"extensions.jupyterlab","JupyterLab plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fjupyterlab-pieces",{"key":257,"label":258,"href":259},"extensions.cli","Pieces CLI","https:\u002F\u002Fpypi.org\u002Fproject\u002Fpieces-cli\u002F",{"key":261,"label":262,"href":263},"docs.home","Documentation home","https:\u002F\u002Fdocs.pieces.app",{"key":265,"label":266,"href":267},"docs.getStarted","Get started docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces",{"key":269,"label":270,"href":271},"docs.api","API docs","https:\u002F\u002Fdocs.pieces.app\u002Fapi",{"key":273,"label":274,"href":275},"docs.desktop.overview","Desktop overview","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop",{"key":277,"label":278,"href":279},"docs.desktop.onboarding","Desktop onboarding","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fonboarding",{"key":281,"label":282,"href":283},"docs.desktop.timeline","Desktop timeline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Ftimeline",{"key":285,"label":286,"href":287},"docs.desktop.summaries","Desktop summaries docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fsingle-click-summaries",{"key":289,"label":290,"href":291},"docs.desktop.search","Desktop conversational search docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconversational-search",{"key":293,"label":294,"href":295},"docs.desktop.drive","Desktop drive docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdrive",{"key":297,"label":298,"href":299},"docs.desktop.account","Desktop account settings docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconfiguration\u002Faccount",{"key":301,"label":302,"href":92},"docs.desktop.download","Desktop download docs",{"key":304,"label":305,"href":306},"docs.piecesOS.overview","PiecesOS overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies",{"key":308,"label":309,"href":310},"docs.piecesOS.details","PiecesOS details docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os",{"key":312,"label":313,"href":215},"docs.piecesOS.install","PiecesOS install docs",{"key":315,"label":316,"href":317},"docs.piecesOS.quickMenu","PiecesOS quick menu docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fquick-menu",{"key":319,"label":320,"href":321},"docs.piecesOS.storage","On-device storage docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fon-device-storage",{"key":323,"label":324,"href":325},"docs.piecesOS.troubleshooting","PiecesOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Ftroubleshooting",{"key":327,"label":328,"href":329},"docs.mcp.overview","MCP overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp",{"key":331,"label":332,"href":333},"docs.mcp.cursor","MCP Cursor docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcursor",{"key":335,"label":336,"href":337},"docs.mcp.vscode","MCP VS Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fvs-code",{"key":339,"label":340,"href":341},"docs.mcp.claudeDesktop","MCP Claude Desktop docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-desktop",{"key":343,"label":344,"href":345},"docs.mcp.claudeCode","MCP Claude Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-code",{"key":347,"label":348,"href":349},"docs.mcp.claudeCowork","MCP Claude Cowork docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-cowork",{"key":351,"label":352,"href":353},"docs.mcp.githubCopilot","MCP GitHub Copilot docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgithub-copilot",{"key":355,"label":356,"href":357},"docs.mcp.goose","MCP Goose docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoose",{"key":359,"label":360,"href":361},"docs.mcp.windsurf","MCP Windsurf docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fwindsurf",{"key":363,"label":364,"href":365},"docs.mcp.zed","MCP Zed docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fzed",{"key":367,"label":368,"href":369},"docs.mcp.jetbrains","MCP JetBrains docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fjetbrains-ides",{"key":371,"label":372,"href":373},"docs.mcp.continueDev","MCP Continue docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcontinue-dev",{"key":375,"label":376,"href":377},"docs.mcp.cline","MCP Cline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcline",{"key":379,"label":380,"href":381},"docs.mcp.raycast","MCP Raycast docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fraycast",{"key":383,"label":384,"href":385},"docs.mcp.rovoDevCli","MCP Rovo Dev CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Frovo-dev-cli",{"key":387,"label":388,"href":389},"docs.mcp.openaiCodexCli","MCP OpenAI Codex CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenai-codex-cli",{"key":391,"label":392,"href":393},"docs.mcp.googleGeminiCli","MCP Google Gemini CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoogle-gemini-cli",{"key":395,"label":396,"href":397},"docs.mcp.amazonQ","MCP Amazon Q docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Famazon-q-developer",{"key":399,"label":400,"href":401},"docs.mcp.chatgptDev","MCP ChatGPT Developer Mode docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fchatgpt-developer-mode",{"key":403,"label":404,"href":405},"docs.mcp.openclaw","MCP OpenClaw docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenclaw",{"key":407,"label":408,"href":409},"docs.mcp.mcpRemote","MCP Remote docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fmcp-remote",{"key":411,"label":412,"href":413},"docs.mcp.ngrok","MCP ngrok docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fngrok-setup",{"key":415,"label":416,"href":417},"docs.troubleshooting.macOS","macOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fmacos",{"key":419,"label":420,"href":421},"docs.troubleshooting.windows","Windows troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fwindows",{"key":423,"label":424,"href":425},"docs.troubleshooting.linux","Linux troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Flinux",{"key":427,"label":428,"href":429},"docs.privacy","Privacy and security docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fprivacy-security-your-data",{"key":431,"label":432,"href":433},"docs.support","Support docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fsupport",{"key":435,"label":436,"href":437},"portal.home","Pieces portal","https:\u002F\u002Fportal.pieces.app",{"key":439,"label":440,"href":441},"site.home","Website home","https:\u002F\u002Fpieces.app",{"key":443,"label":444,"href":445},"site.about","About page","https:\u002F\u002Fpieces.app\u002Fabout",{"key":447,"label":448,"href":449},"site.features","Features page","https:\u002F\u002Fpieces.app\u002Ffeatures",{"key":451,"label":452,"href":453},"site.plugins","Plugins page","https:\u002F\u002Fpieces.app\u002Fplugins",{"key":455,"label":456,"href":457},"site.contact","Contact page","https:\u002F\u002Fpieces.app\u002Fcontact",{"key":459,"label":58,"href":460},"site.updates","https:\u002F\u002Fpieces.app\u002Fupdates",{"key":462,"label":463,"href":464},"site.news","News","https:\u002F\u002Fpieces.app\u002Fnews",{"key":466,"label":467,"href":468},"site.events","Community events","https:\u002F\u002Fpieces.app\u002Fcommunity\u002Fevents",{"key":470,"label":471,"href":472},"site.userStories","User stories","https:\u002F\u002Fpieces.app\u002Fuser-stories",{"key":474,"label":475,"href":476},"site.academy","Academy","https:\u002F\u002Fpieces.app\u002Flearn\u002Facademy",{"key":478,"label":479,"href":480},"site.support","Website support","https:\u002F\u002Fpieces.app\u002Fsupport",{"key":482,"label":483,"href":484},"site.standup","Standup","https:\u002F\u002Fpieces.app\u002Fstandup",{"key":486,"label":34,"href":487},"site.blog","https:\u002F\u002Fcode.pieces.app\u002Fblog",{"key":489,"label":43,"href":490},"social.discord","https:\u002F\u002Fdiscord.gg\u002Fgetpieces",{"key":492,"label":46,"href":493},"social.x","https:\u002F\u002Fx.com\u002Fgetpieces",{"key":495,"label":496,"href":493},"social.twitter","Twitter",{"key":498,"label":499,"href":500},"social.instagram","Instagram","https:\u002F\u002Fwww.instagram.com\u002Fgetpieces\u002F",{"key":502,"label":503,"href":504},"social.tiktok","TikTok","https:\u002F\u002Fwww.tiktok.com\u002F@getpieces",{"key":506,"label":49,"href":507},"social.linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fgetpieces\u002F",{"key":509,"label":510,"href":511},"social.youtube","YouTube","https:\u002F\u002Fyoutube.com\u002F@getpieces",{"key":513,"label":514,"href":515},"github.org","GitHub organization","https:\u002F\u002Fgithub.com\u002Fpieces-app",{"key":517,"label":518,"href":519},"github.support","GitHub support","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport",{"key":521,"label":522,"href":523},"github.issues","GitHub issues","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fissues",{"key":525,"label":526,"href":527},"github.discussions","GitHub discussions","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fdiscussions",{"key":529,"label":530,"href":531},"github.documentation","GitHub documentation","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fdocumentation",{"key":533,"label":534,"href":535},"github.opensource","GitHub open source","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fopensource",{"key":537,"label":538,"href":539},"github.sdks.python","Python SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-python",{"key":541,"label":542,"href":543},"github.sdks.typescript","TypeScript SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-typescript",{"key":545,"label":546,"href":547},"github.sdks.dart","Dart SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-dart",{"key":549,"label":550,"href":551},"github.sdks.kotlin","Kotlin SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-kotlin",{"key":553,"label":554,"href":555},"github.plugins.obsidian","Obsidian plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fobsidian-pieces",{"key":557,"label":558,"href":255},"github.plugins.jupyterlab","JupyterLab plugin repository",{"key":560,"label":561,"href":562},"github.plugins.sublime","Sublime plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_sublime",{"key":564,"label":565,"href":566},"github.plugins.neovim","Neovim plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neovim",{"key":568,"label":569,"href":570},"github.cliAgent","CLI agent repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fcli-agent",{"key":572,"label":573,"href":574},"github.mcpDart","MCP Dart repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fmcp_dart",{"key":576,"label":577,"href":578},"github.awesomePieces","Awesome Pieces repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fawesome-pieces",{"key":580,"label":581,"href":66},"legal.privacyPolicy","Privacy policy",{"key":583,"label":584,"href":69},"legal.refundPolicy","Refund policy",{"key":586,"label":587,"href":72},"legal.terms","Terms",{"key":589,"label":590,"href":591},"legal.security","Legal security","https:\u002F\u002Fpieces.app\u002Flegal\u002Fsecurity",{"key":593,"label":594,"href":511},"videos.youtubeChannel","YouTube channel",{"key":596,"label":597,"href":598},"videos.gettingStartedDesktop","Getting started desktop video","https:\u002F\u002Fyoutu.be\u002FdUr1lRM_TYk",{"key":600,"label":601,"href":602},"videos.snippetDiscoveryxR","Snippet discovery video","https:\u002F\u002Fyoutu.be\u002FG6vb1USw-30",{"key":604,"label":605,"href":606},"sales.bookACall","Book a sales call","https:\u002F\u002Fcalendar.app.google\u002FWVUDtUfNy5Vst3sH7",{"key":608,"label":609,"href":610},"sales.enterprise","Enterprise form","https:\u002F\u002Fgetpieces.typeform.com\u002Fto\u002FaVQFTvpE",{"key":612,"label":613,"href":527},"sales.feedback","Feedback discussions",{"key":615,"label":616,"href":617},"sales.earlyAccess","Early access form","https:\u002F\u002Fgetpieces.typeform.com\u002Fearlyaccess",{"key":619,"label":620,"href":621},"sales.supportEmail","Support email","mailto:support@pieces.app",{"key":623,"label":624,"href":19},"routes.home","Home route",{"key":626,"label":627,"href":56},"routes.about","About route",{"key":629,"label":630,"href":12},"routes.downloads","Downloads route",{"key":632,"label":633,"href":634},"routes.thanks","Post-install thanks route","\u002Finstall\u002Fthanks",{"key":636,"label":637,"href":638},"routes.pricing","Pricing route","\u002Fpricing",{"key":640,"label":641,"href":642},"cta.freeTrial","Start free trial CTA (Pieces Pro) — used by \u002Fcampaigns\u002F* landing pages","https:\u002F\u002Fcampaigns.pieces.app\u002F",{"key":644,"label":645,"href":22},"routes.enterprise","Enterprise route",{"key":647,"label":648,"href":62},"routes.contact","Contact route",{"key":650,"label":651,"href":59},"routes.updates","Updates route",{"key":653,"label":654,"href":655},"routes.migrationError","Migration error route","\u002Fmigration\u002Ferror",{"key":657,"label":658,"href":659},"routes.authenticated","Post-login authenticated route","\u002Fauth\u002Fsigned-in",{"key":661,"label":662,"href":663},"routes.signed-out","Signed out route","\u002Fauth\u002Fsigned-out",{"key":665,"label":666,"href":667},"routes.authentication-error","Authentication error route","\u002Fauth\u002Ferror",{},"data\u002Fshared\u002Furls","2AvfSWI6bZACH5Kr1ZS3txMLjteRjCjbDR_ul1uRZ9Q",{"id":672,"title":673,"author":674,"authorPhoto":675,"authorPhotoAlt":676,"authorSlug":677,"body":678,"buttonText":1317,"buttonUrl":1317,"category":1318,"date":1319,"description":1320,"draft":1321,"editorsPick":1321,"extension":1322,"featured":1321,"image":1323,"imageAlt":1324,"meta":1325,"navigation":32,"ogImage":1317,"ogImageAlt":1317,"path":1326,"seo":1327,"stem":1328,"tags":1317,"__hash__":1329},"blog\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger.md","How to Build and Document a Go REST API with Gin and Go-Swagger","Fatuma Abdullahi","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fauthor.jpeg","Fatuma Abdullahi headshot.","fatuma-abdullahi",{"type":679,"value":680,"toc":1297},"minimark",[681,685,688,693,696,728,732,735,738,753,756,759,762,765,785,788,792,795,798,801,804,815,818,821,827,830,836,839,842,848,851,860,866,869,875,878,881,887,890,893,899,902,905,911,920,923,929,932,935,941,944,948,963,966,973,980,983,986,989,995,998,1001,1007,1010,1016,1019,1022,1028,1031,1035,1038,1043,1046,1057,1060,1064,1073,1076,1079,1085,1088,1091,1097,1100,1103,1109,1112,1118,1121,1129,1132,1138,1141,1147,1150,1156,1159,1165,1168,1171,1177,1180,1183,1189,1196,1202,1211,1214,1220,1223,1227,1230,1233,1237,1240,1244,1247,1251,1254,1258,1261,1264,1267],[682,683,684],"p",{},"In this blog post, we will go over what an API is, how to build a basic Go REST API using the Gin framework, and how to document the API using the go-swagger package.",[682,686,687],{},"We will also go over the importance of API documentation, best practices and API security considerations.",[689,690,692],"h2",{"id":691},"prerequisites","Prerequisites",[682,694,695],{},"To follow along, we will need to have the following:",[697,698,699,709,712,719],"ul",{},[700,701,702],"li",{},[703,704,708],"a",{"href":705,"rel":706},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Finstall",[707],"nofollow","Go installed",[700,710,711],{},"Some understanding of general programming",[700,713,714],{},[703,715,718],{"href":716,"rel":717},"https:\u002F\u002Fblog.hijabicoder.dev\u002Fgolang-syntax-concepts-and-eco-system",[707],"A basic understanding of Go",[700,720,721,722,727],{},"A text editor like ",[703,723,726],{"href":724,"rel":725},"https:\u002F\u002Fcode.visualstudio.com\u002Fdownload",[707],"VS Code"," or another IDE (Integrated Development Environment)",[689,729,731],{"id":730},"what-is-an-api","What is an API?",[682,733,734],{},"The term API stands for Application Programming Interface. It refers to a programming language agnostic contract between systems that need to communicate and pass data around.",[682,736,737],{},"This communication usually happens over the internet via HTTPS. HTTPS is a protocol for moving data in a secure encrypted manner.",[682,739,740,741,746,747,752],{},"The API contract defines behavior and usage. API contracts typically base their definitions on pre-existing specifications such as the ",[703,742,745],{"href":743,"rel":744},"https:\u002F\u002Fspec.openapis.org\u002Foas\u002Flatest.html",[707],"OpenAPI spec"," and the ",[703,748,751],{"href":749,"rel":750},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002FPROTOCOL-HTTP2.md",[707],"GraphQL schema spec",".",[682,754,755],{},"APIs use various communication methodologies including Web Sockets, Remote Procedure Calls (RPC) and REST. In this blog, we will be focussing on and building out a REST API.",[682,757,758],{},"REST stands for Representational State Transfer and is a systems architecture style that defines a set of constraints for designing applications that rely on network connections.",[682,760,761],{},"RESTful APIs conform to these standards and are what sits between the application requesting data over the network and the data itself. The requesting application is referred to as the client, the data being requested is the resource and the application receiving the request is the server. This is the client-server model used by RESTful APIs.",[682,763,764],{},"The client requests the resource via standard HTTP methods commonly known as HTTP verbs. The verbs define what action the client wants to perform on the requested resource. HTTP verbs include:",[697,766,767,770,773,776,779,782],{},[700,768,769],{},"GET - To request the resource",[700,771,772],{},"POST - To add data of the same shape as the resource to the server",[700,774,775],{},"PUT - To update a specified resource or create one if the resource is not there",[700,777,778],{},"DELETE - To delete a specified resource from the server",[700,780,781],{},"PATCH - To modify parts of an existing resource",[700,783,784],{},"HEAD - To retrieve the HTTP headers from the server response",[682,786,787],{},"This article goes deeper into REST, RESTful APIs and HTTP Verbs.",[689,789,791],{"id":790},"how-to-build-a-go-rest-api-using-gin","How To Build a Go REST API Using Gin",[682,793,794],{},"Now that we understand what an API is, let’s go ahead and build a basic blog API using the Go API framework. This blog API will expose a few endpoints, which are specific URIs (Uniform Resource Identifier), to the client.",[682,796,797],{},"These endpoints will correlate with the HTTP verbs and will be dealing with just the one Blog resource. The blog resource shape will contain a unique identifier or an ID, a title, a description, a body, an author and whether or not it is published.",[682,799,800],{},"First, we will need to create a folder or a directory somewhere on the computer. Then open this folder in a preferred text editor or IDE.",[682,802,803],{},"Open the integrated terminal in the text editor and paste the following:",[805,806,811],"pre",{"className":807,"code":809,"language":810},[808],"language-text","go mod init blog-api\n","text",[812,813,809],"code",{"__ignoreMap":814},"",[682,816,817],{},"This will initialize a Go project with “blog-api” being the name of the module. It will also create a go.mod file in the folder that will keep track of project information and any dependencies we might need.",[682,819,820],{},"Next create a main.go file, which will be the entry point of our application, and paste the following in it:",[805,822,825],{"className":823,"code":824,"language":810},[808],"package mainfunc main() {}\n",[812,826,824],{"__ignoreMap":814},[682,828,829],{},"Then create a different folder within the same folder. Name it routes. Create a file called api.go in the routes folder and paste in the following code:",[805,831,834],{"className":832,"code":833,"language":810},[808],"package routes\nimport (\n    \"fmt\"\n    \"net\u002Fhttp\"\n    \"strconv\"\n    \"github.com\u002Fgin-gonic\u002Fgin\"\n)\ntype Blog struct {\n    ID int `json:\"id\"`\n    Title string `json:\"title\"`\n    Description string `json:\"description\"`\n    Body string `json:\"body\"`\n    Author string `json:\"author\"`\n    IsPublished bool `json:\"isPublished\"`\n}\n",[812,835,833],{"__ignoreMap":814},[682,837,838],{},"This code puts our api.go file under the routes package and defines a struct of type Blog which contains an ID of type integer, a title, description and author of type string and an isPublished field of type boolean, which can only hold a true or false value.",[682,840,841],{},"Now paste the following code below the above snippet in the same file:",[805,843,846],{"className":844,"code":845,"language":810},[808],"var blogs = []Blog{\n    {\n        ID:          1,\n        Title:       \"My first blog\",\n        Description: \"This is my first blog\",\n        Body:        \"This is the body of my first blog\",\n        Author:      \"John Doe\",\n        IsPublished: true,\n    },\n    {\n        ID:          2,\n        Title:       \"My second blog\",\n        Description: \"This is my second blog\",\n        Body:        \"This is the body of my second blog\",\n        Author:      \"Jane Doe\",\n        IsPublished: false,\n    },\n    {\n        ID:          3,\n        Title:       \"My third blog\",\n        Description: \"This is my third blog\",\n        Body:        \"This is the body of my third blog\",\n        Author:      \"John Doe\",\n        IsPublished: true,\n    },\n    {\n        ID:          4,\n        Title:       \"My fourth blog\",\n        Description: \"This is my fourth blog\",\n        Body:        \"This is the body of my fourth blog\",\n        Author:      \"Jane Doe\",\n        IsPublished: true,\n    },\n}\n",[812,847,845],{"__ignoreMap":814},[682,849,850],{},"This adds some sample blogs in a slice. This is the resource we will be interacting with using our API to keep focus on our main goal of building and documenting the Go RESTful API. In a real world scenario, we would be interacting with a data store of some sort in order to persist our data.",[682,852,853,854,859],{},"Now let’s define the functions that will be called whenever a request hits our API. All the functions will be referencing the context provided by the ",[703,855,858],{"href":856,"rel":857},"https:\u002F\u002Fgin-gonic.com\u002F",[707],"Gin web framework",". Paste the following code below the sample slice we just added to api.go:",[805,861,864],{"className":862,"code":863,"language":810},[808],"func (b *Blog) GetBlogs(c *gin.Context) {\n c.JSON(http.StatusOK, gin.H{\"status\": http.StatusOK, \"data\":              \nblogs})\n}\n",[812,865,863],{"__ignoreMap":814},[682,867,868],{},"This defines a GetBlogs function as a method of the Blog struct and that takes in a context from Gin. It then returns some JSON containing a HTTP status of OK and the sample blogs as the data.",[805,870,873],{"className":871,"code":872,"language":810},[808],"func (b *Blog) GetBlog(c *gin.Context) {\n    id := c.Param(\"id\")\n    intID, err := strconv.Atoi(id)\n    if err != nil {\n        c.JSON(http.StatusNotFound, gin.H{\"status\":  http.StatusNotFound, \"message\": err.Error()})\n        return\n    }\n    \u002F\u002Ffind blog whose id matched the param id\n    for _, blog := range blogs {\n        if blog.ID == intID {\n            c.JSON(http.StatusOK, gin.H{\"status\": http.StatusOK, \"data\": blog})\n            return\n        }\n    }\n    c.JSON(http.StatusNotFound, gin.H{\"status\": http.StatusNotFound, \"message\": \"Blog not found\"})\n}\n",[812,874,872],{"__ignoreMap":814},[682,876,877],{},"This defines a GetBlog function that is similar to the GetBlogs function except that this one expects an id. It parses the id back into an integer then runs a loop to find the blog from our sample that matches that id. If found it returns it, if not it returns a not found error.",[682,879,880],{},"Now let’s add the functions for creating a new blog and adding it to our blogs as below:",[805,882,885],{"className":883,"code":884,"language":810},[808],"func (b *Blog) CreateBlog(c *gin.Context) {\n    var incomingBlog Blog\n    incomingBlog.ID = len(blogs) + 1\n    err := c.BindJSON(&incomingBlog)\n    if err != nil {\n        c.JSON(http.StatusBadRequest, gin.H{\"status\": http.StatusBadRequest, \"message\": err.Error()})\n        return\n    }\n    blogs = append(blogs, incomingBlog)\n    c.JSON(http.StatusCreated, gin.H{\"status\": http.StatusCreated, \"data\": incomingBlog})\n}\n",[812,886,884],{"__ignoreMap":814},[682,888,889],{},"Here we are expecting a blog resource, assigning it the next number higher than the length of our sample blogs as its ID, then parsing the JSON and appending it onto our slice of blogs. We then return a created status and the newly added blog back to the client. We send back an error message in case of any issues creating the new blog.",[682,891,892],{},"Now let’s add a function to update an existing blog:",[805,894,897],{"className":895,"code":896,"language":810},[808],"func (b *Blog) UpdateBlog(c *gin.Context) {\n    id := c.Param(\"id\")\n    \u002F\u002F Convert the ID to an integer\n    intID, err := strconv.Atoi(id)\n    if err != nil {\n        c.JSON(http.StatusNotFound, gin.H{\"status\": http.StatusNotFound, \"message\": err.Error()})\n        return\n    }\n    \u002F\u002F Find the blog with the matching ID\n    for index, blog := range blogs {\n        if blog.ID == intID {\n            \u002F\u002F Parse the request body to get the updated blog data\n            var updatedBlog Blog\n            err := c.BindJSON(&updatedBlog)\n            if err != nil {\n                c.JSON(http.StatusBadRequest, gin.H{\"status\": http.StatusBadRequest, \"message\": err.Error()})\n                return\n            }\n            \u002F\u002F Update the blog with the new data\n            updatedBlog.ID = intID\n            blogs[index] = updatedBlog\n            \u002F\u002F Respond with the updated blog\n            c.JSON(http.StatusOK, gin.H{\"status\": http.StatusOK, \"data\": updatedBlog})\n            return\n        }\n    }\n    c.JSON(http.StatusNotFound, gin.H{\"status\": http.StatusNotFound, \"message\": \"Blog not found\"})\n}\n",[812,898,896],{"__ignoreMap":814},[682,900,901],{},"This code expects an id, parses it then finds the blog with the matching id. If successful it updates the blog with the changed blog that it received as part of the request body, then returns a status OK message with the updated blog. It also handles errors and sends back relevant messages in the case of any error.",[682,903,904],{},"Finally, let’s add a function to handle the deletion of a blog:",[805,906,909],{"className":907,"code":908,"language":810},[808],"func (b *Blog) DeleteBlog(c *gin.Context) {\n    id := c.Param(\"id\")\n    intID, err := strconv.Atoi(id)\n    if err != nil {\n        c.JSON(http.StatusNotFound, gin.H{\"status\": http.StatusNotFound, \"message\": err.Error()})\n    }\n    for index, blog := range blogs {\n        if blog.ID == intID {\n            blogs = append(blogs[:index], blogs[index+1:]...)\n            c.JSON(http.StatusOK, gin.H{\"status\": http.StatusOK, \"message\": \"Blog deleted successfully\"})\n            return\n        }\n    }\n    c.JSON(http.StatusNotFound, gin.H{\"status\": http.StatusNotFound, \"message\": \"Blog could not be deleted. Blog not found\"})\n}\n",[812,910,908],{"__ignoreMap":814},[682,912,913,914,919],{},"This function looks for the blog with the matching ID then removes it from the slice of blogs. It then sends back to the client a message that the blog was deleted successfully. It handles errors and sends appropriate responses in that case as well. This ",[703,915,918],{"href":916,"rel":917},"https:\u002F\u002Fuser-2aedf2da-fbec-4a16-9b82-ba5eb29c43cd-mr26b5rkmq-uk.a.run.app\u002F?p=acaa47b2dc",[707],"Pieces link"," contains the entire api.go file for easy viewing.",[682,921,922],{},"Back in main.go, paste the following code between the package statement at the top and the function main:",[805,924,927],{"className":925,"code":926,"language":810},[808],"import (\n    \"blog-api\u002Froutes\"\n    \"github.com\u002Fgin-gonic\u002Fgin\"\n  )\n",[812,928,926],{"__ignoreMap":814},[682,930,931],{},"This imports our routes folder and the Gin web framework. We need the Gin web framework as it makes spinning up a web server in Go approachable and is the go-to web framework in the Go eco-system.",[682,933,934],{},"Then paste the following in the main function in the main.go file:",[805,936,939],{"className":937,"code":938,"language":810},[808],"   blog := &routes.Blog{}\n    router := gin.Default()\n    router.GET(\"\u002Fblogs\", blog.GetBlogs)\n    router.GET(\"\u002Fblogs\u002F:id\", blog.GetBlog)\n    router.POST(\"\u002Fblogs\", blog.CreateBlog)\n    router.PUT(\"\u002Fblogs\u002F:id\", blog.UpdateBlog)\n    router.DELETE(\"\u002Fblogs\u002F:id\", blog.DeleteBlog)\n    router.Run(\":8080\")\n",[812,940,938],{"__ignoreMap":814},[682,942,943],{},"This code pulls in our Blog type and initializes the Gin web framework, which is now available in the router variable. It then defines a set of routes, the HTTP verb they expect and the handler function to be executed on each route. It then runs the server on port 8080.For example, the router.POST line says that if the API gets a POST request on the “\u002Fblogs” endpoint then it will execute the UpdateBlog function defined in the api.go file.",[689,945,947],{"id":946},"how-to-test-the-go-rest-api-example-using-thunderclient","How To Test The Go REST API Example Using ThunderClient",[682,949,950,951,956,957,962],{},"Our API is now ready for testing. We can use different ",[703,952,955],{"href":953,"rel":954},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fpractical-guide-api-methods",[707],"API methods"," to test the functionality and behavior of the API including cURL and Postman. For this, we will use a VS Code extension called ",[703,958,961],{"href":959,"rel":960},"https:\u002F\u002Fwww.thunderclient.com\u002F",[707],"ThunderClient",". If using a different text editor, Postman is similar and can be used in place of ThunderClient.",[682,964,965],{},"To add ThunderClient to VS Code, click on the extensions icon in the toolbar and search it in the search bar. Click on the search result and install the extension. After installation, there should be a new ThunderClient icon in the toolbar. Click it to open the interface we will be using to send requests to our API.",[682,967,968,969,972],{},"Open the integrated terminal and run ",[812,970,971],{},"go run main.go"," to start our server. Then in ThunderClient, let’s paste this url “localhost:8080\u002Fblogs” in the bar like in the screenshot below:",[682,974,975],{},[976,977],"img",{"alt":978,"src":979},"Query parameters in VS Code.","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fimg-001.png",[682,981,982],{},"Click “Send” to send the request to our API. We should get the list of blogs we defined in api.go back under the responses tab with a 200 OK status. Add the number 2 after the url and we should now get back only the blog with the id of 2 in the responses tab.",[682,984,985],{},"So far, we have tested the GET route for getting all resources and for getting a specific resource.",[682,987,988],{},"Change the HTTP verb via the dropdown to DELETE like so:",[682,990,991],{},[976,992],{"alt":993,"src":994},"Selecting GET.","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fimg-002.png",[682,996,997],{},"Then click on “Send”. We will get back a message saying that we successfully deleted the blog with id 2. Now let’s change it back to GET and click “Send”, this time we will get an error response saying that the blog was not found.",[682,999,1000],{},"Let’s change the verb to POST and try to add a new blog entry to our list of blogs. Change the URL back to “localhost:\u002F\u002F8080\u002Fblogs” then paste the following in the body section of ThunderClient:",[805,1002,1005],{"className":1003,"code":1004,"language":810},[808],"{\n      \"title\": \"My new blog\",\n      \"description\": \"This is my new  blog\",\n      \"body\": \"This is the body of my new blog\",\n      \"author\": \"John Doe 2\",\n      \"isPublished\": false\n }\n",[812,1006,1004],{"__ignoreMap":814},[682,1008,1009],{},"As shown below:",[682,1011,1012],{},[976,1013],{"alt":1014,"src":1015},"Creating a new request for a Go REST API.","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fimg-003.png",[682,1017,1018],{},"Then click “Send” and we will get back our newly created blog and a created status of 201 meaning that our blog was added to the list of blogs we had defined in api.go.",[682,1020,1021],{},"Finally, let’s test the PUT method of our API. In ThunderClient, let’s change the verb to PUT and change the URL to “localhost:8080\u002Fblogs\u002F1” then paste the following into the body section of ThunderClient:",[805,1023,1026],{"className":1024,"code":1025,"language":810},[808],"{\n  \"title\": \"My first blog EVER\",\n  \"description\": \"This is my first blog\",\n  \"body\": \"This is the body of my first blog\",\n  \"author\": \"John Doe\",\n  \"isPublished\": true\n}\n",[812,1027,1025],{"__ignoreMap":814},[682,1029,1030],{},"Now when we click “Send” we get back the newly updated blog and a 200 OK status. We have now verified that our blog API works as expected.",[689,1032,1034],{"id":1033},"how-to-document-the-api-with-go-swagger","How To Document The API With Go-Swagger",[682,1036,1037],{},"The next step is to document our Go REST API. But why should we document our API anyway?",[1039,1040,1042],"h3",{"id":1041},"importance-of-api-documentation","Importance of API Documentation",[682,1044,1045],{},"Here are some reasons API documentation should never be ignored.",[697,1047,1048,1051,1054],{},[700,1049,1050],{},"Helps other developers quickly understand the API. This makes onboarding new developers easier and they can be productive faster",[700,1052,1053],{},"Makes the API straightforward to version and maintain as it serves as the source of truth",[700,1055,1056],{},"Makes testing the API approachable and clear. This increases chances of the API being tested, increasing security",[682,1058,1059],{},"Documenting APIs also forces decisions regarding the behavior of the API to be well thought through, thereby reducing chances of regressions.",[1039,1061,1063],{"id":1062},"documenting-the-api","Documenting the API",[682,1065,1066,1067,1072],{},"There are several ways to document an API including in an API testing client like ",[703,1068,1071],{"href":1069,"rel":1070},"https:\u002F\u002Fwww.postman.com\u002F",[707],"Postman",", but we will use OpenAPI spec to document. OpenAPI is an evolution of Swagger, which is a popular open source tool for all things API. Swagger comes with a user interface to display, interact and test our API documentation.",[682,1074,1075],{},"In Go, there are several packages to help with our task including Swag but we will use the go-swagger package as it is mainstream, robust and allows us to separate our documentation from the handler functions definitions. This makes documentation readable for the developers who will maintain it.",[682,1077,1078],{},"To install go-swagger, run the following command in the terminal:",[805,1080,1083],{"className":1081,"code":1082,"language":810},[808],"go get -u github.com\u002Fgo-swagger\u002Fgo-swagger\u002Fcmd\u002Fswagger\n",[812,1084,1082],{"__ignoreMap":814},[682,1086,1087],{},"This will get the package for us. Then in the routes folder, add a new file called api_docs.go to hold our documentations. It is important to co-locate this file as it needs to share a package name with the file it is documenting.",[682,1089,1090],{},"Paste the following code in the new file:",[805,1092,1095],{"className":1093,"code":1094,"language":810},[808],"\u002F\u002F Package routes Blog API.\n\u002F\u002F\n\u002F\u002F  Schemes: http\n\u002F\u002F  BasePath: \u002F\n\u002F\u002F  Version: 1.0.0\n\u002F\u002F  Host: localhost:8080\n\u002F\u002F\n\u002F\u002F  Consumes:\n\u002F\u002F  - application\u002Fjson\n\u002F\u002F\n\u002F\u002F  Produces:\n\u002F\u002F  - application\u002Fjson\n\u002F\u002F\n\u002F\u002F swagger:meta\npackage routes\n",[812,1096,1094],{"__ignoreMap":814},[682,1098,1099],{},"This defines the api_docs file as part of the routes package and adds some metadata regarding our API. This includes the scheme it is using, the base path, version, the port it will be running on and the content-type it expects and returns.",[682,1101,1102],{},"Let’s add the following code below the package routes line in the same file:",[805,1104,1107],{"className":1105,"code":1106,"language":810},[808],"func GetBlogs() {}\nfunc GetBlog() {}\nfunc CreateBlog() {}\nfunc UpdateBlog() {}\nfunc DeleteBlog() {}\n",[812,1108,1106],{"__ignoreMap":814},[682,1110,1111],{},"Notice that the functions have the same names as the ones in api.go file and that they are placeholders so that go-swagger can properly determine the relations between the functions and our comments.Now we can add documentation to the placeholder functions in the api_docs file as below:",[805,1113,1116],{"className":1114,"code":1115,"language":810},[808],"\u002F\u002F swagger:route GET \u002Fblogs blogs getBlogs\n\u002F\u002F\n\u002F\u002F GetBlogs returns all blogs.\n\u002F\u002F\n\u002F\u002F Responses:\n\u002F\u002F\n\u002F\u002F  200: successResponse\nfunc GetBlogs() {}\n\u002F\u002F swagger:route GET \u002Fblogs\u002F{id} blogs getBlog\n\u002F\u002F\n\u002F\u002F GetBlog returns a blog by its ID.\n\u002F\u002F\n\u002F\u002F Responses:\n\u002F\u002F\n\u002F\u002F  200: successResponseR\n\u002F\u002F    400: errorResponse\nfunc GetBlog() {}\n\u002F\u002F swagger:route POST \u002Fblogs blogs createBlog\n\u002F\u002F\n\u002F\u002F CreateBlog creates a new blog and returns it.\n\u002F\u002F\n\u002F\u002F Responses:\n\u002F\u002F\n\u002F\u002F  201: successResponse\n\u002F\u002F  400: errorResponse\nfunc CreateBlog() {}\n\u002F\u002F swagger:route PUT \u002Fblogs\u002F{id} blogs updateBlog\n\u002F\u002F\n\u002F\u002F UpdateBlog updates a blog by its ID.\n\u002F\u002F\n\u002F\u002F Responses:\n\u002F\u002F\n\u002F\u002F  200: successResponse\n\u002F\u002F  400: errorResponse\n\u002F\u002F  404: errorResponse\nfunc UpdateBlog() {}\n\u002F\u002F swagger:route DELETE \u002Fblogs\u002F{id} blogs deleteBlog\n\u002F\u002F\n\u002F\u002F DeleteBlog deletes a blog by its ID.\n\u002F\u002F\n\u002F\u002F Responses:\n\u002F\u002F\n\u002F\u002F  200: successResponse\n\u002F\u002F    404: errorResponse\nfunc DeleteBlog() {}\n",[812,1117,1115],{"__ignoreMap":814},[682,1119,1120],{},"Here we are adding specific comments telling go-swagger that the function placeholders are tied to a route via “swagger:route”. We say the HTTP verb associated with each route, describe how the URL will look like, add a short explanation of what the function should do to the requested resource and state all the possible responses each route can return.",[682,1122,1123,1124,1128],{},"For the routes that expect parameters, we specify the name, type, whether it is a required parameter and whether we get it via the URL (path) or via request body. We will need to add more comments and definitions for the success and error responses referenced in the code above. This ",[703,1125,918],{"href":1126,"rel":1127},"https:\u002F\u002Fuser-2aedf2da-fbec-4a16-9b82-ba5eb29c43cd-mr26b5rkmq-uk.a.run.app\u002F?p=2cfe439442",[707]," contains the complete file with the rest of the documentation.",[682,1130,1131],{},"Now, in our api.go let’s replace the struct Blog type with this code that includes its definitions:",[805,1133,1136],{"className":1134,"code":1135,"language":810},[808],"\u002F\u002F Blog represents a blog post with a title, description, body, author, and publication status.\n\u002F\u002F swagger:model\ntype Blog struct {\n    \u002F\u002F The ID of the blog\n    \u002F\u002F\n    \u002F\u002F example: 1\n    ID int `json:\"id\"`\n    \u002F\u002F The title of the blog\n    \u002F\u002F required: true\n    \u002F\u002F example: My first blog\n    Title string `json:\"title\"`\n    \u002F\u002F The description of the blog\n    \u002F\u002F\n    \u002F\u002F example: This is my first blog\n    Description string `json:\"description\"`\n    \u002F\u002F The body of the blog\n    \u002F\u002F required: true\n    \u002F\u002F example: This is the body of my first blog\n    Body string `json:\"body\"`\n    \u002F\u002F The author of the blog\n    \u002F\u002F required: true\n    \u002F\u002F example: John Doe\n    Author string `json:\"author\"`\n    \u002F\u002F The publication status of the blog\n    \u002F\u002F required: true\n    \u002F\u002F example: true\n    IsPublished bool `json:\"isPublished\"`\n}\n",[812,1137,1135],{"__ignoreMap":814},[682,1139,1140],{},"Now that we have our definitions in place, we can ask go-swagger to generate our documentation by running the following command in a new terminal window:",[805,1142,1145],{"className":1143,"code":1144,"language":810},[808],"swagger generate spec -o .\u002Fswagger.json\n",[812,1146,1144],{"__ignoreMap":814},[682,1148,1149],{},"This will generate a swagger.json file at the root of our application folder. If we open it, we will see that go-swagger took our code comments and turned it into json.Let’s now serve Swagger UI to actually see this documentation and try to interact with it by running the following command:",[805,1151,1154],{"className":1152,"code":1153,"language":810},[808],"swagger serve -F=swagger swagger.json\n",[812,1155,1153],{"__ignoreMap":814},[682,1157,1158],{},"This will output in the terminal a port that has our Swagger UI running. Click on the link and we should now be able to interact and see our documentation. It should look something similar to this:",[682,1160,1161],{},[976,1162],{"alt":1163,"src":1164},"Blog API view on Swagger.","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fimg-004.png",[682,1166,1167],{},"We can click on the dropdown arrows and test our API. The UI allows us to make calls to our API, tells us information about the parameters our endpoints expect and shows us the shape of the resource we will be interacting with. We can even see the error and success states..",[682,1169,1170],{},"But if we try to interact with the Swagger UI we will run into a CORS error as below:",[682,1172,1173],{},[976,1174],{"alt":1175,"src":1176},"CORS error in Swagger UI.","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fimg-005.png",[682,1178,1179],{},"CORS refers to Cross-Origin Resource Sharing. It is a security feature in web browsers that determines how resources on different domains interact. The error in the screenshot above is because our server is running on port 8080 and our Swagger UI is not. The UI is making requests to our server from a different origin than the server itself and our server did not specify that this was allowed.",[682,1181,1182],{},"We can fix that by adding support for CORS in our main.go file. Let’s replace the import in main.go with the following:",[805,1184,1187],{"className":1185,"code":1186,"language":810},[808],"ain.go with the following:\nimport (\n    \"blog-api\u002Froutes\"\n    \"github.com\u002Fgin-gonic\u002Fgin\"\n    cors \"github.com\u002Frs\u002Fcors\u002Fwrapper\u002Fgin\"\n)\n",[812,1188,1186],{"__ignoreMap":814},[682,1190,1191,1192,1195],{},"Then add the following code in the main function below the ",[812,1193,1194],{},"router := gin.Default()"," statement and above the route declarations:",[805,1197,1200],{"className":1198,"code":1199,"language":810},[808],"    corsConfig := cors.New(cors.Options{\n        AllowedOrigins:   []string{\"*\"},\n        AllowedMethods:   []string{\"GET\", \"POST\", \"PUT\", \"DELETE\"},\n        AllowedHeaders:   []string{\"Origin\", \"Content-Type\"},\n        AllowCredentials: true,\n    })\n    router.Use(corsConfig)\n",[812,1201,1199],{"__ignoreMap":814},[682,1203,1204,1205,1210],{},"This tells the browser that our server allows access from all domains and for all methods. This is what the ",[703,1206,1209],{"href":1207,"rel":1208},"https:\u002F\u002Fuser-2aedf2da-fbec-4a16-9b82-ba5eb29c43cd-mr26b5rkmq-uk.a.run.app\u002F?p=b1554ea66e",[707],"main.go file"," should be like at this point.",[682,1212,1213],{},"Now, if we rerun our main.go file and try out our Swagger UI, we should get the proper response like so:",[682,1215,1216],{},[976,1217],{"alt":1218,"src":1219},"Creating the REST API.","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fimg-006.png",[682,1221,1222],{},"We can go ahead and retest our API via the UI and verify that everything works as expected.",[689,1224,1226],{"id":1225},"api-security-basics","API Security Basics",[682,1228,1229],{},"There are some considerations we can take to make our API more secure and robust. For example, allowing our server to accept requests from any origin is not good practice. In reality, we restrict access to domains we know about.",[682,1231,1232],{},"Here are some security measures we can take to protect our API and our data:",[1039,1234,1236],{"id":1235},"authentication-and-authorisation","Authentication and Authorisation",[682,1238,1239],{},"We could add an authentication layer to the API and expect client applications to identify and verify themselves before allowing access to our resources. This reduces chances of API abuse.",[1039,1241,1243],{"id":1242},"https","HTTPS",[682,1245,1246],{},"We could have our API only accessible via HTTPS which is encrypted and more secure than HTTP. This drastically increases the integrity of our API as it is harder to exploit and eavesdrop on information over an encrypted connection.",[1039,1248,1250],{"id":1249},"rate-limiting","Rate Limiting",[682,1252,1253],{},"We could add a rate limit to our API that blocks access to our resources for a particular client after a certain number of requests in a set time period.This protects our API from abuse.",[689,1255,1257],{"id":1256},"wrapping-up","Wrapping Up",[682,1259,1260],{},"We have learned about building an API in Go (along with a Go Gin API example), and how to document it using go-swagger and OpenAPI spec. We have also learned the importance of API documentation and API security basics.",[1039,1262,27],{"id":1263},"resources",[682,1265,1266],{},"Looking to build your own Go REST API? Here are some helpful resources and further reading:",[697,1268,1269,1276,1283,1290],{},[700,1270,1271],{},[703,1272,1275],{"href":1273,"rel":1274},"https:\u002F\u002Fswagger.io\u002Fspecification\u002F",[707],"OpenAPI Spec Documentation",[700,1277,1278],{},[703,1279,1282],{"href":1280,"rel":1281},"https:\u002F\u002Fblurify.com\u002Fblog\u002Fbest-practices-for-building-robust-web-api-architecture\u002F",[707],"Best Practices on Web Architecture",[700,1284,1285],{},[703,1286,1289],{"href":1287,"rel":1288},"https:\u002F\u002Fwww.heavy.ai\u002Ftechnical-glossary\u002Fclient-server#:~:text=What%20is%20the%20Client%2DServer,computer%20network%20or%20the%20Internet.",[707],"On the Client-Server Model",[700,1291,1292],{},[703,1293,1296],{"href":1294,"rel":1295},"https:\u002F\u002Fpieces.app\u002Fblog\u002Funderstanding-go-reflection-interfaces",[707],"A deeper dive into Go",{"title":814,"searchDepth":1298,"depth":1298,"links":1299},2,[1300,1301,1302,1303,1304,1309,1314],{"id":691,"depth":1298,"text":692},{"id":730,"depth":1298,"text":731},{"id":790,"depth":1298,"text":791},{"id":946,"depth":1298,"text":947},{"id":1033,"depth":1298,"text":1034,"children":1305},[1306,1308],{"id":1041,"depth":1307,"text":1042},3,{"id":1062,"depth":1307,"text":1063},{"id":1225,"depth":1298,"text":1226,"children":1310},[1311,1312,1313],{"id":1235,"depth":1307,"text":1236},{"id":1242,"depth":1307,"text":1243},{"id":1249,"depth":1307,"text":1250},{"id":1256,"depth":1298,"text":1257,"children":1315},[1316],{"id":1263,"depth":1307,"text":27},null,"Software Development","2024-03-08T00:00:00.000Z","Understand what an API is, how to build a Go REST API using Gin, and how to document APIs using Go-Swagger.",false,"md","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger\u002Fhero.jpeg","Building and documenting APIs with a badger.",{},"\u002Fblog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger",{"title":673,"description":1320},"blog\u002Fhow-to-build-and-document-a-go-rest-api-with-gin-and-go-swagger","qNgKf4KnH0p0VJuxSNbRIAZ4o1HhV1gRHPF2Vz_Cc4I",{"id":1331,"title":674,"body":1332,"description":814,"draft":1321,"extension":1322,"meta":1336,"navigation":32,"path":1337,"photo":1338,"photoAlt":1317,"seo":1339,"stem":1340,"__hash__":1341},"authors\u002Fauthors\u002Ffatuma-abdullahi.md",{"type":679,"value":1333,"toc":1334},[],{"title":814,"searchDepth":1298,"depth":1298,"links":1335},[],{},"\u002Fauthors\u002Ffatuma-abdullahi","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fauthors\u002Ffatuma-abdullahi.jpg",{"title":674,"description":814},"authors\u002Ffatuma-abdullahi","aLujDqabApGFGQqxhv3MuOYY56NoRUBUPgifHzzIHSI",{"left":1343,"top":1343,"width":1344,"height":1344,"rotate":1343,"vFlip":1321,"hFlip":1321,"body":1345},0,24,"\u003Cpath fill=\"currentColor\" d=\"m7.825 13l4.9 4.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-6.6-6.6q-.15-.15-.213-.325T4.426 12t.063-.375t.212-.325l6.6-6.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L7.825 11H19q.425 0 .713.288T20 12t-.288.713T19 13z\"\u002F>"]