DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
API Development

How to Build an API with Go: A Practical REST Tutorial

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a small Go API by defining resource-shaped endpoints, creating a Go module, decoding and encoding JSON, and wiring handlers to a router. This tutorial uses Gin for a complete album API, then shows the equivalent Go 1.22+ net/http routing. The example stores data in memory so you can focus on HTTP; a real service should replace that slice with a database and add an explicit production design for authentication, authorization, limits, observability and deployment.

What you will build

The finished service exposes three REST endpoints for an album resource:

Method Path Purpose
GET /albums Return every album
POST /albums Validate and create an album
GET /albums/{id} (or /albums/:id in Gin) Return one album by ID

The official Go learning material uses a similar album example and notes that its slice-backed storage is a teaching simplification; a typical API reads and writes a database.

Prerequisites and project setup

Install Go and create a module

Use a current Go installation. Go modules record your module path and dependencies. Create a directory and initialize it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
mkdir go-albums
cd go-albums
go mod init example.com/go-albums

If you choose Gin, add it to the module:

go get github.com/gin-gonic/gin

Create main.go in this directory. The module path is an example; use the import path you control if you publish the project.

Build the API with Gin

Define the resource and seed data

Keep the wire format explicit with JSON tags. The mutex prevents concurrent requests from racing while this example modifies its in-memory slice.

package main

import (
    "net/http"
    "sync"

    "github.com/gin-gonic/gin"
)

type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

var (
    albums = []album{
        {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
        {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
        {ID: "3", Title: "Sarah Vaughan", Artist: "Sarah Vaughan", Price: 39.99},
    }
    albumsMu sync.RWMutex
)

Add list, create and lookup handlers

ShouldBindJSON decodes the request body and returns a client error for malformed JSON or a type mismatch. The handlers return JSON with a status that describes the outcome.

func getAlbums(c *gin.Context) {
    albumsMu.RLock()
    defer albumsMu.RUnlock()
    c.JSON(http.StatusOK, albums)
}

func postAlbums(c *gin.Context) {
    var newAlbum album
    if err := c.ShouldBindJSON(&newAlbum); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON: " + err.Error()})
        return
    }
    if newAlbum.ID == "" || newAlbum.Title == "" || newAlbum.Artist == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "id, title and artist are required"})
        return
    }

    albumsMu.Lock()
    defer albumsMu.Unlock()
    for _, existing := range albums {
        if existing.ID == newAlbum.ID {
            c.JSON(http.StatusConflict, gin.H{"error": "id already exists"})
            return
        }
    }
    albums = append(albums, newAlbum)
    c.JSON(http.StatusCreated, newAlbum)
}

func getAlbumByID(c *gin.Context) {
    id := c.Param("id")
    albumsMu.RLock()
    defer albumsMu.RUnlock()
    for _, a := range albums {
        if a.ID == id {
            c.JSON(http.StatusOK, a)
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "album not found"})
}

Register routes and start the server

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.POST("/albums", postAlbums)
    router.GET("/albums/:id", getAlbumByID)

    if err := router.Run(":8080"); err != nil {
        panic(err)
    }
}

Run it with:

go run .

Gin listens on http://localhost:8080 by default in this example. The framework’s tutorial uses the same list/create/fetch-by-ID shape and JSON responses. Gin also provides middleware and other abstractions you can add as the service grows.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Exercise each endpoint

curl http://localhost:8080/albums

curl http://localhost:8080/albums/2

curl -i -X POST http://localhost:8080/albums 
  -H 'Content-Type: application/json' 
  -d '{"id":"4","title":"Kind of Blue","artist":"Miles Davis","price":29.99}'

The first request returns an array, the second returns one object, and the POST returns 201 Created with the newly stored album. Try an unknown ID to see 404 Not Found, or send invalid JSON to receive 400 Bad Request.

Gin or the Go standard library?

Go 1.22 added method matching and wildcard path segments to net/http‘s ServeMux. A wildcard value is available through Request.PathValue. The Go team’s routing article describes this as one fewer dependency for many projects, while also saying third-party frameworks remain suitable for advanced routing needs.

Choose Fits when Trade-off
Go 1.22+ net/http Your routes mainly need HTTP methods and path wildcards, and you prefer the standard library. You assemble more application conventions yourself.
Gin You want the official Go tutorial’s framework-based path, middleware and framework APIs. You add a dependency and follow Gin’s abstractions.

Neither choice is a universal winner. Start with ServeMux for a small dependency-free service; use Gin when its middleware and routing model match your application.

A dependency-free Go 1.22 example

This shorter server demonstrates the same endpoints with method patterns. The {id} wildcard is read with PathValue.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type album struct {
    ID string `json:"id"`
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /albums", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, []album{{ID: "1"}})
    })
    mux.HandleFunc("GET /albums/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        if id != "1" {
            writeJSON(w, http.StatusNotFound, map[string]string{"error": "album not found"})
            return
        }
        writeJSON(w, http.StatusOK, album{ID: id})
    })
    log.Fatal(http.ListenAndServe(":8080", mux))
}

func writeJSON(w http.ResponseWriter, status int, value any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    _ = json.NewEncoder(w).Encode(value)
}

This is intentionally minimal: add request decoding, validation and persistence before treating it as a complete application.

Design decisions to make before expanding the API

Resource and status semantics

  • Use plural resource paths consistently, such as /albums.
  • Define which fields are required and reject malformed or incomplete JSON with a documented client error.
  • Return a consistent error object instead of mixing plain text and JSON.
  • Choose status codes deliberately: successful reads, creation, invalid input, conflicts and missing resources should be distinguishable to clients.

Persistence

The slice disappears whenever the process restarts and is not suitable for multiple instances. Replace it with a relational or other persistent database, put database access behind a small repository or service boundary, and test failure paths such as unavailable storage and duplicate keys. The Go tutorial index separates REST, JSON and relational-database learning for this reason.

Compatibility and evolution

Decide how clients discover the API’s contract, whether paths need a version prefix, and how you will add fields without breaking older clients. Keep JSON names stable once clients depend on them.

Production-readiness checklist

The tutorial demonstrates request handling, not a complete deployment architecture. Before exposing an API, make explicit decisions about:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Authentication and authorization: who may call each route and whether a caller may access a particular record.
  • Transport and secrets: HTTPS termination, key storage and rotation, and safe handling of sensitive request data.
  • Input and resource limits: body size, deadlines, pagination, expensive operations and rate controls.
  • Operations: structured logs, useful metrics, traces where appropriate, health checks and alerting.
  • Lifecycle: graceful shutdown, configuration, migrations, backups and a rollback plan.
  • Verification: unit tests for handlers and services, integration tests against a real database, and contract tests for clients.

These are design areas to validate for your environment, not features supplied automatically by either Gin or net/http.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

go: no required module provides package

Run the command from the directory containing go.mod. For Gin, run go get github.com/gin-gonic/gin, then go mod tidy.

Every request returns 404

Check the method as well as the path. POST /albums is different from GET /albums. In Gin, use /:id; in Go 1.22 patterns, use /{id} and read it with PathValue.

POST returns a binding or JSON error

Send Content-Type: application/json and valid JSON. Property names must match the struct tags, and a quoted number cannot be decoded into a numeric field without changing the type or payload.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

New data vanishes

That is expected with the in-memory slice. Use persistent storage and define how startup migrations and connection failures are handled.

Data races appear under load

Shared mutable state needs synchronization; the Gin sample uses an RWMutex only for demonstration. A database-backed service should also define transaction and consistency behavior rather than relying on process memory.

The standard-library server does not compile

Method patterns and PathValue require Go 1.22 or newer. Upgrade Go, or use a compatible routing approach for an older toolchain.

Or skip the browser setup

If you need a clean screenshot of a rendered API documentation page or demo, ScreenshotNeo provides a single HTTP call. Replace the example URL with your deployed documentation URL; this concrete call captures ScreenshotNeo’s documentation page:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://screenshotneo.com/docs/ -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://screenshotneo.com/docs/"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://screenshotneo.com/docs/' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for request options. Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether the request was billed. Its MCP server lets AI agents such as Claude or Cursor call take_screenshot, get_page_info and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Next steps

  1. Move the album slice behind a database-backed service.
  2. Separate transport handlers from validation and persistence code.
  3. Write tests for successful, malformed, unauthorized and not-found requests.
  4. Document the JSON contract and operational decisions before deployment.
  5. Choose Gin or Go 1.22+ net/http based on the routing and framework features your project actually needs.

Frequently Asked Questions

Which Go version supports wildcard values in ServeMux?

Go 1.22 introduced method patterns and wildcard segments for the standard net/http router; handlers read a segment with Request.PathValue.

Can this example handle multiple server instances?

Not as written. Its slice is process-local memory, so instances do not share changes. Use shared persistent storage and define transaction behavior before scaling out.

Do I have to use Gin to build a Go API?

No. Go 1.22+ net/http can cover method-and-path routing without a framework. Gin remains a reasonable choice when its middleware and abstractions fit your needs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.