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
This commit is contained in:
Gabriel Radureau
2026-04-03 13:04:33 +02:00
parent 04d34bead1
commit 6365f92359
8 changed files with 134 additions and 4 deletions

View File

@@ -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))
}

15
cmd/server/main.go Normal file
View File

@@ -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())
}

2
go.mod
View File

@@ -1,3 +1,5 @@
module DanceLessonsCoach
go 1.26.1
require github.com/go-chi/chi/v5 v5.2.5 // indirect

2
go.sum Normal file
View File

@@ -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=

44
pkg/greet/api_v1.go Normal file
View File

@@ -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})
}

View File

@@ -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!"
}

View File

@@ -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)
}

58
pkg/server/server.go Normal file
View File

@@ -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
}