Commands
Commands are app entry points for developer, operator, and application workflows.
They run through the App lifecycle, use injected dependencies, and are exposed through forj and the App binary.
Running Commands
Use the command name directly for the default app:
forj route:list
forj reports:reconcile
forj worker
forj schedulerUse the app name first to select an additional app:
forj admin route:list
forj admin reports:reconcile
forj admin worker
forj admin schedulerInside a GoForj Project, native GoForj commands take precedence. If no native command matches, GoForj delegates to the active App. Use forj run <command> when you want to force default App command execution explicitly, and use ./bin/<app> <command> when running a built binary.
The command runs inside the App, not as an ad hoc shell script around it.
Create a Command
Create a command for the default App:
forj make:command reports:reconcilePrefix the generator when an additional app owns the command:
forj admin make:command reports:reconcileBuild and run the generated command before replacing its starter behavior:
forj build
go test ./...
forj reports:reconcileExpected output includes:
ReconcileCmd executed!For an additional app, use forj admin build and forj admin reports:reconcile.
Replace the starter body with the application workflow and add its service to the constructor. Wire will satisfy the new dependency after its provider is in the App service set.
The make:command reference covers output overrides, removal, and the exact registration changes. Keep command code focused on flags, input translation, output, and calling application services.
Command Responsibilities
Commands are a good fit for:
- explicit operator tasks
- one-off maintenance actions
- local development utilities
- data reconciliation
- bootstrap tasks
- running runtime boundaries such as workers or schedulers
Commands should not become unstructured backdoors around application services.
Context and Cancellation
Generated commands can receive the CLI lifecycle context directly. Pass it to the service instead of replacing it with context.Background():
// Run delegates reconciliation to the service while preserving CLI cancellation.
func (c *ReconcileCmd) Run(ctx context.Context) error {
return c.service.Reconcile(ctx)
}Long-running services should check cancellation between units of work and pass the same context into repositories and clients:
// Reconcile processes pending reports until the work completes or the caller cancels.
func (s *Service) Reconcile(ctx context.Context) error {
reportIDs, err := s.reports.PendingIDs(ctx)
if err != nil {
return err
}
for _, reportID := range reportIDs {
if err := ctx.Err(); err != nil {
return err
}
if err := s.reconcileOne(ctx, reportID); err != nil {
return err
}
}
return nil
}The CLI context is cancelled when the command lifecycle stops, including interrupt-driven shutdown. Runtime commands such as HTTP, queue workers, and the scheduler receive the same lifecycle-managed cancellation behavior.
Use context.Background() only at a boundary that genuinely has no caller context. A generated command's Run(ctx context.Context) method already has one.
Next Steps
make:commandReference shows generation, placement, and wiring.- Naming Conventions defines stable command names.
- Application Services explains where command behavior should delegate.
- Wiring Recipes shows the command wiring flow.
- App Lifecycle explains command startup and shutdown.
- Testing explains how command tests fit the broader test strategy.
- Console provides standalone messages, ANSI-aware layout, tables, prompts, loaders, and progress for command output.