The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →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:
#1 Best Overall
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.
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.
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:
Rank #4
- 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.
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.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallBest Value
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.
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
- Move the album slice behind a database-backed service.
- Separate transport handlers from validation and persistence code.
- Write tests for successful, malformed, unauthorized and not-found requests.
- Document the JSON contract and operational decisions before deployment.
- Choose Gin or Go 1.22+
net/httpbased 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.
Recommended Free Tools
Quick Recap
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.




