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 message generation logic is handled by the Greeter service, which is injected into the NewGreeterRouteHandler function.
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.
package modules
import (
"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](registry)
_ = registration.RegisterTransient(registry, route_handlers.NewGreeterRouteHandler)
return nil
}
This configuration ensures that all required route handlers are correctly registered and injected into the Chi router instance. Since the application service expects a set of route handler services, the RegisterList method is 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. While aspects like configurable listener ports or graceful server shutdown are omitted for simplicity, they could be addressed within this service as well.
Running the Application
To run the application, navigate to its root directory and execute the following command:
go run main.go
As configured, 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.