Best examples of REST API with Go: Practical Examples in Go
Let’s start with the smallest example of a REST API in Go that still feels realistic: an in-memory “todo” service using only the standard library.
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
)
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
var todos = []Todo{
{ID: 1, Title: "Learn Go", Done: false},
{ID: 2, Title: "Build REST API", Done: false},
}
func main() {
http.HandleFunc("/todos", handleTodos)
http.HandleFunc("/todos/", handleTodoByID)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleTodos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
json.NewEncoder(w).Encode(todos)
case http.MethodPost:
var t Todo
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
t.ID = len(todos) + 1
todos = append(todos, t)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(t)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func handleTodoByID(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
idStr := r.URL.Path[len("/todos/"):]
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
for i, t := range todos {
if t.ID == id {
switch r.Method {
case http.MethodGet:
json.NewEncoder(w).Encode(t)
case http.MethodDelete:
todos = append(todos[:i], todos[i+1:]...)
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
return
}
}
http.Error(w, "not found", http.StatusNotFound)
}
This is one of the best examples if you want to understand the bare minimum: how to register handlers, read the path, decode JSON, and write responses. It’s not pretty, but it’s honest.
Using a router: examples include chi and gorilla-style APIs
Once you’ve built a couple of examples of REST API with Go: practical examples using net/http, you’ll probably want nicer routing. In 2024, the chi router is a popular choice for lightweight, idiomatic APIs.
import (
"encoding/json"
"log"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
type User struct {
ID int `json:"id"`
Email string `json:"email"`
}
var users = []User{{ID: 1, Email: "alice@example.com"}}
func main() {
r := chi.NewRouter()
// built-in middleware: logging, recovery, request IDs
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Route("/api", func(api chi.Router) {
api.Get("/users", listUsers)
api.Post("/users", createUser)
api.Get("/users/{id}", getUser)
})
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
func listUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func createUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
u.ID = len(users) + 1
users = append(users, u)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(u)
}
func getUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
idStr := chi.URLParam(r, "id")
// parse id, find user, return JSON (similar to todo example)
}
This example of a REST API with Go highlights:
- Cleaner route definitions with path parameters.
- Middleware for logging and panic recovery.
- A structure that scales as you add more endpoints.
For real-world security considerations around user data, you can cross-check patterns with privacy guidance from organizations like the U.S. Federal Trade Commission.
JSON validation and error handling: real examples that won’t embarrass you in prod
Many tutorials skip validation. That’s how you end up with weird bugs and security issues. In this section, examples of REST API with Go focus on validating requests and returning consistent errors.
type CreateUserRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func writeError(w http.ResponseWriter, status int, code, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(APIError{Code: code, Message: msg})
}
func createUserValidated(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid_json", "Invalid JSON body")
return
}
if req.Email == "" || req.Password == "" {
writeError(w, http.StatusBadRequest, "missing_fields", "Email and password are required")
return
}
if len(req.Password) < 12 {
writeError(w, http.StatusBadRequest, "weak_password", "Password must be at least 12 characters")
return
}
// create user in DB here...
w.WriteHeader(http.StatusCreated)
}
This is one of the best examples if you care about:
- Clear separation between transport errors and business logic.
- Consistent error shapes for frontend teams.
- Simple but realistic password rules (inspired by security guidance from sources like NIST).
Database-backed examples of REST API with Go: practical examples with PostgreSQL
In reality, your API won’t stay in memory. Let’s wire up PostgreSQL using database/sql and pgx as the driver. This example of a REST API with Go shows a small “articles” service.
import (
"context"
"database/sql"
"encoding/json"
"log"
"net/http"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
)
type Article struct {
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
}
type App struct {
DB *sql.DB
}
func main() {
db, err := sql.Open("pgx", "postgres://user:pass@localhost:5432/blog?sslmode=disable")
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
app := &App{DB: db}
http.HandleFunc("/articles", app.handleArticles)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func (a *App) handleArticles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case http.MethodGet:
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
rows, err := a.DB.QueryContext(ctx,
`SELECT id, title, body, created_at FROM articles ORDER BY created_at DESC LIMIT 50`)
if err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
defer rows.Close()
var articles []Article
for rows.Next() {
var art Article
if err := rows.Scan(&art.ID, &art.Title, &art.Body, &art.CreatedAt); err != nil {
http.Error(w, "scan error", http.StatusInternalServerError)
return
}
articles = append(articles, art)
}
json.NewEncoder(w).Encode(articles)
case http.MethodPost:
var req struct {
Title string `json:"title"`
Body string `json:"body"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
var id int
err := a.DB.QueryRowContext(ctx,
`INSERT INTO articles (title, body) VALUES (\(1, \)2) RETURNING id`,
req.Title, req.Body,
).Scan(&id)
if err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]int{"id": id})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
This is where examples of REST API with Go start to look like actual backend services: context timeouts, connection pools, and proper error handling.
Auth and JWT: examples include simple token-based APIs
Authentication is where many Go beginners get stuck. Let’s walk through a stripped-down but practical example of JWT auth middleware.
import (
"context"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v5"
)
type ctxKey string
const userIDKey ctxKey = "userID"
var jwtSecret = []byte("super-secret-key-change-me")
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth == "" || !strings.HasPrefix(auth, "Bearer ") {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}
tokenStr := strings.TrimPrefix(auth, "Bearer ")
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return jwtSecret, nil
})
if err != nil || !token.Valid {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
http.Error(w, "invalid claims", http.StatusUnauthorized)
return
}
userID, _ := claims["sub"].(string)
ctx := context.WithValue(r.Context(), userIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func CurrentUserHandler(w http.ResponseWriter, r *http.Request) {
id, _ := r.Context().Value(userIDKey).(string)
if id == "" {
http.Error(w, "no user", http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"user_id": id})
}
This example of REST API with Go shows:
- How to plug authentication into a router as middleware.
- How to pass user identity through
context.Context. - How to build endpoints that rely on the authenticated user.
For security best practices around tokens and sessions, it’s worth reading guidance from organizations like OWASP.
Versioning and structuring: examples of REST API with Go for long-lived services
Once your API survives a few production releases, you’ll need versioning. Here’s a small but realistic layout using chi that shows how examples of REST API with Go can be organized for the long haul.
func Routes() http.Handler {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Route("/api", func(api chi.Router) {
api.Route("/v1", func(v1 chi.Router) {
v1.Mount("/users", usersV1Routes())
v1.Mount("/articles", articlesV1Routes())
})
api.Route("/v2", func(v2 chi.Router) {
v2.Mount("/users", usersV2Routes()) // maybe different response shapes
})
})
return r
}
func usersV1Routes() chi.Router {
r := chi.NewRouter()
r.Get("/", listUsersV1)
r.Post("/", createUserV1)
return r
}
func usersV2Routes() chi.Router {
r := chi.NewRouter()
r.Get("/", listUsersV2) // e.g., includes pagination metadata
return r
}
This style gives you:
- Clean separation between versions.
- The ability to ship new behavior without breaking old clients.
- A clear mental model when you add more examples of REST API with Go: practical examples over time.
Observability: real examples with logging, metrics, and health checks
Modern APIs live in noisy environments: Kubernetes, serverless, or managed platforms. You need observability baked in. Here are small, focused examples of REST API with Go that add health checks and metrics.
Health check handler
func healthHandler(w http.ResponseWriter, r *http.Request) {
// In a real service, you might check DB, cache, external APIs, etc.
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
})
}
You can wire this to /healthz and let Kubernetes or your load balancer use it for readiness checks.
Prometheus metrics example
import (
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
r := chi.NewRouter()
r.Handle("/metrics", promhttp.Handler())
r.Get("/healthz", healthHandler)
// other routes...
log.Fatal(http.ListenAndServe(":8080", r))
}
These real examples show how a Go REST API can expose metrics that monitoring systems scrape. For background on why observability matters in production systems, the U.S. National Institute of Standards and Technology (NIST) publishes guidance on system reliability and monitoring that’s worth a look.
2024–2025 trends: how these examples of REST API with Go fit modern stacks
In 2024–2025, Go REST APIs are showing up in a few recurring patterns:
- As lightweight microservices behind API gateways.
- As backends for React/Next.js frontends.
- As internal APIs orchestrating data pipelines and ML services.
The examples of REST API with Go: practical examples you’ve seen here map directly to those patterns:
- The in-memory and router-based examples are perfect for small internal tools.
- The PostgreSQL-backed example is basically a starter kit for a production CRUD service.
- The JWT and versioning examples include the patterns you’ll see in real SaaS backends.
- The observability examples plug into Prometheus, Grafana, and cloud monitoring.
If you’re building APIs that touch regulated data (for example, health data in the U.S.), it’s worth understanding standards like HIPAA and reading from health-focused sources such as the National Institutes of Health or Mayo Clinic to design endpoints and access controls responsibly.
FAQ: short, honest answers about Go REST APIs
What are some real examples of REST API with Go used in production?
Real-world examples include internal microservices at cloud providers, payment processing backends, analytics ingestion services, and high-traffic APIs in companies that value performance and simplicity. Many engineering blogs (for example, from large tech companies) describe migrating parts of their stack to Go for HTTP services.
Can you show an example of testing a REST API handler in Go?
Here’s a minimal test using net/http/httptest:
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestListUsers(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/users", nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(listUsers)
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", rr.Code)
}
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
t.Fatalf("expected application/json, got %s", ct)
}
}
This example of a test keeps everything in-memory and fast, which is exactly what you want for unit tests.
Are frameworks necessary, or can I stick to the standard library?
You can absolutely build production APIs using only net/http. Many of the best examples of REST API with Go in open source projects rely mostly on the standard library plus a router like chi. Frameworks can help, but they’re optional, not mandatory.
How do I document a Go REST API?
Popular options include OpenAPI/Swagger with generators that scan your handlers and comments. You can also maintain markdown docs by hand if your surface area is small. The key is to keep examples, request/response shapes, and error codes aligned with the actual handlers so that your examples of REST API with Go: practical examples remain trustworthy.
How do I handle pagination in Go REST APIs?
A common pattern is to accept page and page_size query parameters, apply LIMIT and OFFSET in SQL, and return metadata fields like total and next_page. This plays nicely with frontend frameworks and is easy to express in Go structs.
The bottom line: if you study and adapt these examples of REST API with Go: practical examples, you’ll have a solid starter kit for everything from hobby projects to serious production services.
Related Topics
Practical examples of Go command-line application examples for 2025
Practical examples of 3 examples of working with Go maps for real projects
Practical examples of simple HTTP server examples in Go
Practical examples of defining and using structs in Go
Practical examples of creating and using Go interfaces
So You Think You Know HTTP in Go? Think Again
Explore More Go Code Snippets
Discover more examples and insights in this category.
View All Go Code Snippets