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.
Generate a Controller
Name the controller for the HTTP area it owns:
forj make:controller reportsFor an additional app, prefix the generator with the app name:
forj admin make:controller reportsReplace the starter response with a thin HTTP adapter around an application service:
package reports
import (
"net/http"
"github.com/goforj/web"
)
// Controller translates report HTTP requests into service calls.
type Controller struct {
service *Service
}
// NewController constructs the report HTTP adapter.
func NewController(service *Service) *Controller {
return &Controller{service: service}
}
// Routes declares the report endpoints owned by this controller.
func (c *Controller) Routes() []web.Route {
return []web.Route{
web.NewRoute(http.MethodGet, "/reports/:id", c.Show),
}
}
// Show returns one report by ID.
func (c *Controller) Show(ctx web.Context) error {
report, err := c.service.Find(ctx.Context(), ctx.Param("id"))
if err != nil {
return err
}
return ctx.JSON(http.StatusOK, report)
}For a redirect response, use the same controller boundary and return ctx.Redirect(http.StatusFound, target). Response status and location belong to the HTTP adapter; deciding where the workflow should send a user can remain service-owned.
The make command wires the controller constructor. Add the application service to app/wire/inject_services_app.go:
// appSet provides application-level services and dependencies.
var appSet = wire.NewSet(
// existing framework and app providers...
reports.NewService,
)In the normal flow, you do not hand-edit the controller provider set just to make the new controller constructible. Use -d only when you intentionally want to override the package directory.
Verify the Result
Run the full verification after the implementation and service provider are in place:
forj build
go test ./...
forj route:listExpected result: forj build regenerates the graph, go test ./... passes, and route:list includes /reports/:id. Start forj api and request the route to prove the public response.
For an additional app, run forj admin route:list after the build to verify its routes.
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. The implementation above keeps its required service visible in NewController; follow that constructor-injection shape instead of reaching through global state. Model optional collaborators 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.
Next Steps
- JSON API Route follows a complete controller, service, test, build, route-list, and request workflow.
make:controllerReference 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.