Controllers
A Controller is an HTTP-facing type that groups related route handlers.
Controllers should translate requests into application service calls and translate service results into responses. Business workflows belong in services, jobs, or domain-owned types.
Controller Shape
package users
import (
"net/http"
"github.com/goforj/web"
)
type Controller struct {
service *Service
}
func NewController(service *Service) *Controller {
return &Controller{service: service}
}
func (c *Controller) Routes() []web.Route {
return []web.Route{
web.NewRoute(http.MethodGet, "/users/:id", c.Show),
}
}
func (c *Controller) Show(ctx web.Context) error {
user, err := c.service.Find(ctx.Context(), ctx.Param("id"))
if err != nil {
return err
}
return ctx.JSON(http.StatusOK, user)
}Make Commands
Use forj make:controller when starting a new controller:
forj make:controller UsersThe make command generates the controller and injects it into the generated HTTP wiring surfaces. In the normal flow, you do not hand-edit the controller provider set just to make the new controller constructible.
Use grouped names to place controllers with the package they belong to:
forj make:controller billing:reportsThis creates internal/billing/reports/controller.go, wires the controller constructor, and registers the controller routes. Use -d only when you intentionally want to override the package directory.
Review what the make command created or updated:
internal/users/controller.goowns the controller type, constructor, handlers, and route list.wire/inject_http_controllers.goprovides the controller constructor.internal/router/routes_registry.goincludes the controller routes in the App route registry.
If the controller depends on a service, make sure the service constructor is wired from wire/inject_app_services.go. The make command wires the controller; the service provider still belongs in the app services set.
var appSet = wire.NewSet(
// existing framework and app providers...
users.NewService,
)The controller itself belongs in the HTTP controller set. The make command adds this provider for you:
var httpAppControllerSet = wire.NewSet(
// existing controllers...
users.NewController,
)Run:
forj build
forj route:listforj build verifies the generated graph. route:list verifies the controller routes are registered where the App can serve them.
Responsibilities
Controllers should own:
- path parameters
- query parameters
- request binding
- request validation handoff
- service calls
- response shaping
- HTTP status decisions
Controllers should not own long-running business workflows, persistence details, queue worker behavior, or infrastructure construction.
Dependency Injection
Controllers are constructed through providers and Wire.
Inject services, not global state:
func NewController(service *Service) *Controller {
return &Controller{service: service}
}If the service is required, keep it visible in the constructor. Optional collaborators should be modeled explicitly.
Request Context
Use ctx.Context() when passing cancellation and deadlines into services:
report, err := c.service.Generate(ctx.Context(), input)Use web.Context for HTTP-specific behavior such as params, binding, response helpers, request metadata, and response writing.
Common Mistakes
Common mistakes
- Do not put business workflows directly in controllers.
- Do not import backend driver packages into controllers.
- Do not use controllers as service locators.
- Do not hide validation failures behind generic internal errors.
- Do not depend on the underlying HTTP engine in normal App controllers.
Next Steps
- Make Commands explains grouped package placement and generated wiring updates.
- Wiring Recipes shows the controller wiring flow.
- Requests and Validation explains request input boundaries.
- Responses and Errors explains response shape.
- Application Services explains where business behavior belongs.
