Walkthrough: Parsley Integration with Chi
This guide demonstrates how to integrate the Parsley dependency injection framework with the Chi web framework. By following this example, you'll learn how to set up a Chi application with dependency injection managed by Parsley, making your codebase more modular, testable, and maintainable.
Project Structure
The code for this example can be found at examples/integrations/chi. It has the following structure:
chi
├── cmd
│ └── main.go
└── internal
├── application.go
├── modules
│ ├── chi_module.go
│ ├── greeter_module.go
│ └── route_handlers_module.go
├── route_handlers
│ ├── greeter.go
│ └── types.go
└── services
└── greeter.go
This article describes the project's structure and the purpose of each module in detail.
Main Application
The main entry point of the application is in the cmd/main.go file:
package main
import (
"context"
"github.com/matzefriedrich/parsley-docs/examples/integrations/chi/internal"
"github.com/matzefriedrich/parsley-docs/examples/integrations/chi/internal/modules"
"github.com/matzefriedrich/parsley/pkg/bootstrap"
)
func main() {
ctx := context.Background()
err := bootstrap.RunParsleyApplication(ctx, internal.NewApp,
modules.ConfigureChi,
modules.ConfigureGreeter)
if err != nil {
panic(err)
}
}
In this file, the RunParsleyApplication function is called to bootstrap the application. It initializes the Parsley application context and configures the Chi server with the necessary services and route handlers.
Modules
The modules package contains the service configurations required to set up the Chi application. The chi_module.go module configures the Chi router and registers it as a singleton service within the Parsley framework:
package modules
import (
"github.com/go-chi/chi/v5"
"github.com/matzefriedrich/parsley/pkg/types"
)
var _ types.ModuleFunc = ConfigureChi
// ConfigureChi Configures all services required by the chi app.
func ConfigureChi(registry types.ServiceRegistry) error {
_ = registry.Register(newChi, types.LifetimeSingleton)
_ = registry.RegisterModule(RegisterRouteHandlers)
return nil
}
func newChi() chi.Router {
return chi.NewRouter()
}
This configuration ensures that the Chi router is initialized and available for dependency injection.
Services
Next, the internal/services/greeter.go file defines the Greeter service, which returns a greeting message based on the provided name and politeness flag.
package services
import "fmt"
type Greeter interface {
SayHello(name string, polite bool) string
}
type greeter struct {
}
func (g *greeter) SayHello(name string, polite bool) string {
if polite {
return fmt.Sprintf("Good day, %s!\n", name)
}
return fmt.Sprintf("Hi, %s\n", name)
}
func NewGreeter() Greeter {
return &greeter{}
}
The Greeter service is registered by the greeter_module.go module.
package modules
import (
"github.com/matzefriedrich/parsley-docs/examples/integrations/chi/internal/services"
"github.com/matzefriedrich/parsley/pkg/types"
)
// ConfigureGreeter Configures the services.Greeter service dependencies.
func ConfigureGreeter(registry types.ServiceRegistry) error {
registry.Register(services.NewGreeter, types.LifetimeTransient)
return nil
}
Route Handlers
In this example, route handlers are also services, structs implementing the RouteHandler interface, which register one or more route handlers with the Chi router. The interface is defined as follows:
package route_handlers
import "github.com/go-chi/chi/v5"
type RouteHandler interface {
Register(router chi.Router)
}
The internal/route_handlers/greeter.go file registers the route handler for the /say-hello endpoint, which returns a greeting message based on the query parameters provided in the request. The logic for the message generation is handled by the Greeter service, which is injected into the NewGreeterRouteHandler method.
package route_handlers
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/matzefriedrich/parsley-docs/examples/integrations/chi/internal/services"
)
type greeterRouteHandler struct {
greeter services.Greeter
}
// Register Registers all greeterRouteHandler route handlers.
func (h *greeterRouteHandler) Register(router chi.Router) {
router.Get("/say-hello", h.HandleSayHelloRequest)
}
// HandleSayHelloRequest Handles "GET /say-hello" requests.
func (h *greeterRouteHandler) HandleSayHelloRequest(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
msg := h.greeter.SayHello(name, true)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(msg))
}
var _ RouteHandler = &greeterRouteHandler{}
func NewGreeterRouteHandler(greeter services.Greeter) RouteHandler {
return &greeterRouteHandler{
greeter: greeter,
}
}
The route_handler_module.go file handles the registration of the RouteHandler services themselves.
package modules
import (
"context"
"github.com/matzefriedrich/parsley-docs/examples/integrations/chi/internal/route_handlers"
"github.com/matzefriedrich/parsley/pkg/features"
"github.com/matzefriedrich/parsley/pkg/registration"
"github.com/matzefriedrich/parsley/pkg/types"
)
// RegisterRouteHandlers Registers all route handlers of the chi app.
func RegisterRouteHandlers(registry types.ServiceRegistry) error {
_ = features.RegisterList[route_handlers.RouteHandler](context.Background(), registry)
_ = registration.RegisterTransient(registry, route_handlers.NewGreeterRouteHandler)
return nil
}
This configuration ensures that all route handlers the application requires are correctly registered and injected into the Chi router instance. Since the application service expects a set of route handler services, the RegisterList method must be used to register a list activator for the RouteHandler type.
Application Logic
The internal/application.go file contains the main application service:
package internal
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/matzefriedrich/parsley-docs/examples/integrations/chi/internal/route_handlers"
"github.com/matzefriedrich/parsley/pkg/bootstrap"
)
type parsleyApplication struct {
router chi.Router
}
var _ bootstrap.Application = &parsleyApplication{}
// NewApp Creates the main application service instance. This constructor function gets invoked by Parsley; add parameters for all required services.
func NewApp(router chi.Router, routeHandlers []route_handlers.RouteHandler) bootstrap.Application {
for _, routeHandler := range routeHandlers {
routeHandler.Register(router)
}
return &parsleyApplication{
router: router,
}
}
// Run The entrypoint for the Parsley application.
func (a *parsleyApplication) Run(_ context.Context) error {
return http.ListenAndServe(":5504", a.router)
}
This file defines the parsleyApplication struct as the main application service. It registers the route handlers and starts the Chi server on port 5504. However, aspects like having the listener port configurable or a graceful server shutdown are omitted here, but they could be addressed here as well.
Running the Application
To run the application, navigate to the root directory and execute the following command:
go run main.go
As configured in the code, the application will start a Chi server on http://localhost:5504. You can then access the /say-hello endpoint:
curl "http://localhost:5504/say-hello?name=John"
This should return:
Good day, John!
For more details on Parsley, check out the Parsley GitHub repository.