In this example we are going to create an interface for HTTP handlers to standardise its use and error handling. It will help simplify error and response handling for API calls. It has been kept at minimal but you should improve it so don't take it as-is.


main.go


package main

import (
"log"

"random/http"
"random/users"
)

func main() {
rtr := http.NewRouter()
rtr.Add("/api/v1/users", users.Handler)

srv := http.NewServer(rtr.Handler, ":3000")
log.Fatal(srv.ListenAndServe())
}

http/server.go


package http

import (
"net/http"
)

func NewServer(handler http.Handler, address string) *http.Server {
return &http.Server{
Handler: handler,
Addr: address,
}
}


http/router.go


package http

import "net/http"

type Router struct {
Handler *http.ServeMux
}

func NewRouter() *Router {
return &Router{
Handler: http.NewServeMux(),
}
}

func (r *Router) Add(path string, hnd handler) {
r.Handler.Handle(path, handler(hnd))
}

http/handler.go


package http

import (
"errors"
"fmt"
"net/http"
)

type handler func(http.ResponseWriter, *http.Request) error

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := h(w, r)
if err == nil {
return
}

var e Error
if !errors.As(err, &e) {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(e.Status)
w.Write([]byte(fmt.Sprintf("code: %s, message: %s", e.Code, e.Error())))
}

http/error.go


package http

type Error struct {
Status int
Code string
Message string
}

func (e Error) Error() string {
return e.Message
}

domain/error.go


package domain

import "random/http"

var (
ErrInternalError = http.Error{Status: 500, Code: "internal_error", Message: "An internal error has occurred"}
ErrInvalidRequest = http.Error{Status: 400, Code: "invalid_request", Message: "Request is invalid"}
ErrResourceConflict = http.Error{Status: 409, Code: "resource_conflict", Message: "A resource conflict has been detected"}
ErrResourceNotFound = http.Error{Status: 404, Code: "resource_not_found", Message: "Resource is not found"}
)

users/api.go


package users

import (
"fmt"
"net/http"
"random/domain"
)

func Handler(w http.ResponseWriter, r *http.Request) error {
id := r.Header.Get("id")
if id == "" {
return domain.ErrInvalidRequest
}
if id == "0" {
return domain.ErrResourceNotFound
}
if id == "1" {
return domain.ErrResourceConflict
}

w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("I am user with id %s", id)))

return nil
}