Terraform on Naver Cloud - Developing Go API
I plan to develop a Cafe CRUD system in Go Lang based on the Go API I previously analyzed. I intend to follow a step-by-step development approach similar to the existing workflow.
main.go
cafeHandler := handlers.NewCafe(db, logger)
r.Handle("/cafes", cafeHandler).Methods("GET")
r.Handle("/cafes/{id:[0-9]+}", cafeHandler).Methods("GET")
r.HandleFunc("/cafes", cafeHandler.CreateCafe).Methods("POST")
r.HandleFunc("/cafes/{id:[0-9]+}", cafeHandler.UpdateCafe).Methods("PUT")
r.HandleFunc("/cafes/{id:[0-9]+}", cafeHandler.DeleteCafe).Methods("DELETE")
Looking at main.go, the GET method uses the Handle function, whereas other methods use the HandleFunc function. What is the difference between these two functions, and why are they used differently?
Handle Method
func (r *Router) Handle(path string, handler http.Handler) *Route {
return r.NewRoute().Path(path).Handler(handler)
}
Handle(path string, handler http.Handler) *Route- This method takes an
http.Handlerinterface as an argument and registers the path and handler. http.Handlerrefers to any type that implements theServeHTTP(ResponseWriter, *Request)method.- Example:
r.Handle("/path", someHandler).
HandleFunc Method
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *Route {
return r.NewRoute().Path(path).HandlerFunc(f)
}
HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *Route- This method takes a function-type handler as an argument and registers the path and function.
- The function receives
http.ResponseWriterand*http.Requestas parameters.
Example:
r.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {...})
The key difference between these two methods is that Handle takes an http.Handler type, whereas HandleFunc takes a function-type handler. Thus, in the case of the GET request, cafeHandler as a whole can handle the request, while other requests require separate functions, leading to the use of HandleFunc.
Handlers/cafe.go
As implemented in main.go, I will define handlers for the cafe-related API endpoints. Each function will be designed to handle requests for a specific HTTP method.
Struct and Constructor
// Cafe - Struct for the Cafe handler
type Cafe struct {
con data.Connection // Database connection object
log hclog.Logger // Logging object
}
// NewCafe - Constructor function for the Cafe struct
func NewCafe(con data.Connection, l hclog.Logger) *Cafe {
return &Cafe{con, l}
}
- The
Cafestruct hascon(database connection) andlog(logging) as attributes. NewCafecreates and returns an instance of Cafe. ServeHTTP Method Since thehttp.Handlerinterface has aServeHTTP(http.ResponseWriter, *http.Request)method, theCafestruct must implement this method to satisfy the interface. Therefore, I will implement theReadfunction insideServeHTTPto handleGETrequests.
func (c *Cafe) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
c.log.Info("Handle Cafe")
vars := mux.Vars(r)
var cafeID *int
if vars["id"] != "" {
cId, err := strconv.Atoi(vars["id"])
if err != nil {
c.log.Error("Cafe ID could not be converted to an integer", "error", err)
http.Error(rw, "Invalid cafe ID", http.StatusInternalServerError)
return
}
cafeID = &cId
}
- The request is processed through
mux.Vars(r), which extracts URL path parameters into a map. - For example, if the URL path is
/cafes/{id}and the request is/cafes/123,mux.Vars(r)will return{"id": "123"}. - The
varsmap is used to access theid, which is then assigned tocafeID.
cofs, err := c.con.GetCafes(cafeID)
if err != nil {
c.log.Error("Unable to get cafes from database", "error", err)
http.Error(rw, "Unable to list cafes", http.StatusInternalServerError)
return
}
var d []byte
d, err = json.Marshal(cofs)
if err != nil {
c.log.Error("Unable to convert cafes to JSON", "error", err)
http.Error(rw, "Unable to list cafes", http.StatusInternalServerError)
return
}
rw.Write(d)
}
- Using
cafeID, theGetCafesfunction ofconis called to retrieve data. - The retrieved data is then converted to JSON and written as a response.
CreateCafe Method
func (c *Cafe) CreateCafe(rw http.ResponseWriter, r *http.Request) {
c.log.Info("Handle Cafe | CreateCafe")
var cafes []model.Cafe
reqBody, _ := io.ReadAll(r.Body)
c.log.Info("Request Body", "body", string(reqBody))
r.Body = io.NopCloser(bytes.NewBuffer(reqBody)) // Reset request body
err := json.NewDecoder(r.Body).Decode(&cafes)
if err != nil {
c.log.Error("Unable to decode JSON", "error", err)
http.Error(rw, "Unable to parse request body", http.StatusInternalServerError)
return
}
- The request body is read using
io.ReadAll. - The body is then parsed into the
cafesvariable, an array ofmodel.Cafe.c.log.Info("Decoded Body", "body", cafes) cafe := cafes[0] createdCafe, err := c.con.CreateCafe(cafe) if err != nil { c.log.Error("Unable to create new cafe", "error", err) http.Error(rw, fmt.Sprintf("Unable to create new cafe: %s", err.Error()), http.StatusInternalServerError) return } - Since only one cafe object is needed for creation, cafes[0] is used.
Troubleshooting:
During development, I faced compatibility issues between object arrays and single objects. Despite needing a single object, I often mistakenly used arrays. This needs to be refactored.
d, err := createdCafe.ToJSON()
if err != nil {
c.log.Error("Unable to convert cafe to JSON", "error", err)
http.Error(rw, "Unable to create new cafe", http.StatusInternalServerError)
return
}
rw.Write(d)
}
-
The newly created cafe object is converted to JSON and returned in the response.
UpdateCafe Method
func (c *Cafe) UpdateCafe(rw http.ResponseWriter, r *http.Request) {
c.log.Info("Handle Cafe | UpdateCafe")
vars := mux.Vars(r)
body := model.Cafe{}
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
c.log.Error("Unable to decode JSON", "error", err)
http.Error(rw, "Unable to parse request body", http.StatusInternalServerError)
return
}
cafeID, err := strconv.Atoi(vars["id"])
if err != nil {
c.log.Error("Cafe ID could not be converted to an integer", "error", err)
http.Error(rw, "Unable to update cafe", http.StatusInternalServerError)
return
}
cafe, err := c.con.UpdateCafe(cafeID, body)
-
Similar to CreateCafe, but UpdateCafe retrieves id and updates the corresponding cafe record.
This document provides a step-by-step explanation of implementing a Cafe CRUD system in Go Lang using an API. Let me know if you need any modifications! 😊
Leave a comment