Skip to main content
Backend functions depend on runtime services (loggers, clocks, DB handles). Providers declare those dependencies with use and wire them at entry points with with. Request data stays separate from shared infrastructure.

Two primitives

Parameters carry data (order IDs, request bodies). Providers carry services the host supplies (logger, clock, repo). Keeping them separate clarifies what flows through your API boundary and what is ambient context.

Declare dependencies with use

Contracts are ordinary types: shapes with methods, or imported Go interfaces:
The compiler aggregates use sites into Providers(f) for each function and propagates requirements transitively through calls. You can see the obligation chain in diagnostics and LSP hovers: which services a handler needs, and which entry point must supply them.

Wire at the boundary

Entry points satisfy every Provider the call graph needs using a with { … } { … } block. Production wiring and test wiring use the same mechanism:
Wiring keys use the root contract name (Logger, Clock). Values must structurally satisfy the contract: plain structs with matching methods, or Go types that implement an imported interface.

Void contract methods

Contract methods without a return type (such as info(msg String)) must stay void on implementations. A trailing fmt.Println(...) in the method body can infer a non-void Result return from Go’s (int, error) signature; the compiler reports that mismatch. Use println for side-effect logging in void provider methods, or declare an explicit return type if you intend to return the call result. See Return inference and implicit returns.

Shadowing

Nested with blocks merge with outer wiring. When the inner block supplies the same contract key, it shadows the outer value for that scope; other keys still forward from outside:

Tests

Tests reuse the same with blocks to swap fakes without a separate DI framework:

Compared to other approaches

The Providers RFC surveyed these patterns before settling on use and with. See the Providers RFC hub for the full prior art writeup. Forst treats context.Context separately from Providers. Declare services with use so requirements stay typed and visible. Use context for cancellation and request scoped values. Use with blocks for service wiring. Sidecar and TypeScript exports stay data only. The host wires Providers in Go before exposing a handler.

What gets emitted

Providers lower to a deduped struct parameter at the Go boundary. Production and test wiring compile to ordinary struct literals: readable Go, no runtime container.

Caveats

Providers are experimental. The RFC surface is still evolving. Pin your compiler version and run task example:providers when upgrading.

Host wiring only

Providers lower in Go. They are not exported to TypeScript or sidecar invoke payloads. Functions with unsatisfied Providers(f) are omitted from generated clients. Wire with blocks in Go main before exposing handlers. See Call Forst from Node § Caveats and Generate client types § Caveats.

Void contract methods

A trailing fmt.Println(...) in a void provider method can infer a non-void Result return from Go’s (int, error) signature. Use println for side effect logging, or declare an explicit return type. See Return inference and implicit returns.

context.Context is separate

Providers are typed services declared with use. Cancellation and request scoped values stay in context, not in Provider wiring.

Try it

From the Forst repository:
Deep dive: Providers RFC hub

Go interop

Import Go interfaces as Provider contracts.

Editor workflow

LSP hovers show Provider obligations and related diagnostics.