Building RESTful APIs in Go

1. Setting Up the Project

Directory Structure:

lua
myproject/ |-- cmd/ | |-- myproject/ | |-- main.go |-- internal/ | |-- handlers/ | | |-- handlers.go | |-- models/ | |-- models.go |-- pkg/ | |-- middleware/ | |-- logging.go |-- go.mod |-- go.sum

Initializing the Project:

sh
go mod init myproject

2. Defining Models

Create your models in internal/models/models.go.

go
package models type Item struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` Price float64 `json:"price"` }

3. Creating Handlers

Implement the handlers in internal/handlers/handlers.go.

go
package handlers import ( "encoding/json" "net/http" "myproject/internal/models" "strconv" "github.com/gorilla/mux" ) var items []models.Item func GetItems(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(items) } func GetItem(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id, err := strconv.Atoi(params["id"]) if err != nil { http.Error(w, "Invalid item ID", http.StatusBadRequest) return } for _, item := range items { if item.ID == id { json.NewEncoder(w).Encode(item) return } } http.Error(w, "Item not found", http.StatusNotFound) } func CreateItem(w http.ResponseWriter, r *http.Request) { var item models.Item _ = json.NewDecoder(r.Body).Decode(&item) item.ID = len(items) + 1 items = append(items, item) json.NewEncoder(w).Encode(item) } func UpdateItem(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id, err := strconv.Atoi(params["id"]) if err != nil { http.Error(w, "Invalid item ID", http.StatusBadRequest) return } for index, item := range items { if item.ID == id { items = append(items[:index], items[index+1:]...) var updatedItem models.Item _ = json.NewDecoder(r.Body).Decode(&updatedItem) updatedItem.ID = id items = append(items, updatedItem) json.NewEncoder(w).Encode(updatedItem) return } } http.Error(w, "Item not found", http.StatusNotFound) } func DeleteItem(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id, err := strconv.Atoi(params["id"]) if err != nil { http.Error(w, "Invalid item ID", http.StatusBadRequest) return } for index, item := range items { if item.ID == id { items = append(items[:index], items[index+1:]...) break } } json.NewEncoder(w).Encode(items) }

4. Setting Up the Router

Set up the router in cmd/myproject/main.go.

go
package main import ( "log" "myproject/internal/handlers" "net/http" "github.com/gorilla/mux" ) func main() { router := mux.NewRouter() router.HandleFunc("/items", handlers.GetItems).Methods("GET") router.HandleFunc("/items/{id}", handlers.GetItem).Methods("GET") router.HandleFunc("/items", handlers.CreateItem).Methods("POST") router.HandleFunc("/items/{id}", handlers.UpdateItem).Methods("PUT") router.HandleFunc("/items/{id}", handlers.DeleteItem).Methods("DELETE") log.Fatal(http.ListenAndServe(":8000", router)) }

5. Adding Middleware

Add logging middleware in pkg/middleware/logging.go.

go
package middleware import ( "log" "net/http" "time" ) func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) log.Printf("%s %s %v", r.Method, r.RequestURI, time.Since(start)) }) }

Apply the middleware in main.go.

go
router.Use(middleware.LoggingMiddleware)

6. Testing the API

Use tools like Postman or cURL to test your endpoints.

GET all items:

sh
curl -X GET http://localhost:8000/items

GET a single item:

sh
curl -X GET http://localhost:8000/items/1

Create a new item:

sh
curl -X POST http://localhost:8000/items -d '{"name":"Item1","description":"A new item","price":10.99}' -H "Content-Type: application/json"

Update an item:

sh
curl -X PUT http://localhost:8000/items/1 -d '{"name":"Updated Item","description":"Updated description","price":12.99}' -H "Content-Type: application/json"

Delete an item:

sh
curl -X DELETE http://localhost:8000/items/1

7. Error Handling

Enhance error handling in your handlers.

go
func CreateItem(w http.ResponseWriter, r *http.Request) { var item models.Item if err := json.NewDecoder(r.Body).Decode(&item); err != nil { http.Error(w, "Invalid input", http.StatusBadRequest) return } item.ID = len(items) + 1 items = append(items, item) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(item) }

8. Securing the API

Implement authentication and authorization mechanisms such as JWT or OAuth2.

Example with JWT:

go
func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if token == "" || !validateToken(token) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) }

Apply the middleware to routes that require authentication.

go
router.HandleFunc("/items", handlers.GetItems).Methods("GET") router.Handle("/items", AuthMiddleware(http.HandlerFunc(handlers.CreateItem))).Methods("POST")

9. Documentation

Use tools like Swagger to document your API.

Swagger Integration:

  1. Install go-swagger:

    sh
    go get -u github.com/go-swagger/go-swagger/cmd/swagger
  2. Add comments to your handlers:

    go
    // swagger:route GET /items items listItems // // Lists all items. // // Responses: // 200: itemsResponse func GetItems(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(items) }
  3. Generate the documentation:

    sh
    swagger generate spec -o ./swagger.yaml --scan-models
  4. Serve the Swagger UI with your API.

By following these steps, you'll have a robust and maintainable RESTful API built in Go, capable of handling real-world requirements and scalable for future growth.

Becoming a Senior Go Developer: Mastering Go and Its Ecosystem