From 6365f923591a71f375f93687e189bcad897d843c Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Fri, 3 Apr 2026 13:04:33 +0200 Subject: [PATCH] Refactor to JSON API with Chi router and interface-based design - Add pkg/greet/api_v1.go with ApiV1Greet interface and handler - Implement Greeter interface for dependency injection - Return JSON responses with proper Content-Type headers - Move greet service instantiation to server package - Separate v1 route registration into registerApiV1Routes function - Health endpoint at /api level, greet endpoints at /api/v1/greet - Simplify server entrypoint by removing external dependencies --- cmd/greet/main.go | 3 ++- cmd/server/main.go | 15 +++++++++++ go.mod | 2 ++ go.sum | 2 ++ pkg/greet/api_v1.go | 44 +++++++++++++++++++++++++++++++ pkg/greet/greet.go | 9 ++++++- pkg/greet/greet_test.go | 5 ++-- pkg/server/server.go | 58 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 cmd/server/main.go create mode 100644 go.sum create mode 100644 pkg/greet/api_v1.go create mode 100644 pkg/server/server.go diff --git a/cmd/greet/main.go b/cmd/greet/main.go index 9162540..18334da 100644 --- a/cmd/greet/main.go +++ b/cmd/greet/main.go @@ -8,10 +8,11 @@ import ( ) func main() { + service := greet.NewService() name := "" if len(os.Args) > 1 { name = os.Args[1] } - fmt.Println(greet.Greet(name)) + fmt.Println(service.Greet(name)) } \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..1f429b1 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "net/http" + + "DanceLessonsCoach/pkg/server" +) + +func main() { + server := server.NewServer() + + fmt.Println("Server running on :8080") + http.ListenAndServe(":8080", server.Router()) +} \ No newline at end of file diff --git a/go.mod b/go.mod index d984988..0710536 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module DanceLessonsCoach go 1.26.1 + +require github.com/go-chi/chi/v5 v5.2.5 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a4ac04e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= diff --git a/pkg/greet/api_v1.go b/pkg/greet/api_v1.go new file mode 100644 index 0000000..1cd96a2 --- /dev/null +++ b/pkg/greet/api_v1.go @@ -0,0 +1,44 @@ +package greet + +import ( + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" +) + +type Greeter interface { + Greet(name string) string +} + +type ApiV1Greet interface { + RegisterRoutes(router chi.Router) +} + +type apiV1GreetHandler struct { + greeter Greeter +} + +func NewApiV1GreetHandler(greeter Greeter) ApiV1Greet { + return &apiV1GreetHandler{greeter: greeter} +} + +func (h *apiV1GreetHandler) RegisterRoutes(router chi.Router) { + router.Get("/", h.handleGreetQuery) + router.Get("/{name}", h.handleGreetPath) +} + +func (h *apiV1GreetHandler) handleGreetQuery(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + h.writeJSONResponse(w, h.greeter.Greet(name)) +} + +func (h *apiV1GreetHandler) handleGreetPath(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + h.writeJSONResponse(w, h.greeter.Greet(name)) +} + +func (h *apiV1GreetHandler) writeJSONResponse(w http.ResponseWriter, message string) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"message": message}) +} \ No newline at end of file diff --git a/pkg/greet/greet.go b/pkg/greet/greet.go index 2f48369..cb41160 100644 --- a/pkg/greet/greet.go +++ b/pkg/greet/greet.go @@ -1,8 +1,15 @@ package greet +type Service struct{} + +func NewService() *Service { + return &Service{} +} + // Greet returns a greeting message for the given name. // If name is empty, it defaults to "world". -func Greet(name string) string { +// Implements the Greeter interface. +func (s *Service) Greet(name string) string { if name == "" { return "Hello world!" } diff --git a/pkg/greet/greet_test.go b/pkg/greet/greet_test.go index c67d1b7..fce159c 100644 --- a/pkg/greet/greet_test.go +++ b/pkg/greet/greet_test.go @@ -2,7 +2,8 @@ package greet import "testing" -func TestGreet(t *testing.T) { +func TestService_Greet(t *testing.T) { + service := NewService() tests := []struct { name string expected string @@ -15,7 +16,7 @@ func TestGreet(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := Greet(tt.name) + result := service.Greet(tt.name) if result != tt.expected { t.Errorf("Greet(%q) = %q, want %q", tt.name, result, tt.expected) } diff --git a/pkg/server/server.go b/pkg/server/server.go new file mode 100644 index 0000000..3cabdb3 --- /dev/null +++ b/pkg/server/server.go @@ -0,0 +1,58 @@ +package server + +import ( + "net/http" + + "DanceLessonsCoach/pkg/greet" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +type Server struct { + router *chi.Mux +} + +func NewServer() *Server { + s := &Server{ + router: chi.NewRouter(), + } + s.setupRoutes() + return s +} + +func (s *Server) setupRoutes() { + s.router.Use(middleware.Logger) + s.router.Route("/api", func(r chi.Router) { + r.Use(s.apiMiddlewares()...) + r.Get("/health", s.handleHealth) + s.registerApiV1Routes(r) + }) +} + +func (s *Server) registerApiV1Routes(r chi.Router) { + r.Route("/v1", func(r chi.Router) { + + greetService := greet.NewService() + greetHandler := greet.NewApiV1GreetHandler(greetService) + r.Route("/greet", func(r chi.Router) { + greetHandler.RegisterRoutes(r) + }) + + }) +} + +func (s *Server) apiMiddlewares() []func(http.Handler) http.Handler { + return []func(http.Handler) http.Handler{ + middleware.StripSlashes, + middleware.Recoverer, + } +} + +func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"status":"healthy"}`)) +} + +func (s *Server) Router() http.Handler { + return s.router +}