An insert-only client is a River client that inserts jobs but doesn’t work them. This is a common pattern where frontend or API processes enqueue work, and a separate pool of worker processes picks it up.
Setup
An insert-only client is simply one that’s never started with Start. The Queues and Workers fields are omitted from its config (though Workers may be included for validation as described below):
insertOnlyClient, err := river.NewClient(riverpgxv5.New(dbPool), &river.Config{})if err != nil { panic(err)}That’s it. The client is ready to insert jobs with Insert or InsertTx:
_, err = insertOnlyClient.InsertTx(ctx, tx, SortArgs{ Strings: []string{ "whale", "tiger", "bear", },}, nil)if err != nil { panic(err)}Configure an empty queue map to create a client for insertion without local consumers:
client = River::Client.new(driver, config: River::Config.new(queues: {}))client.insert SortArgs.new(strings: %w[whale tiger bear])Build a client without queues or workers and use it directly for insertion; there’s no need to call start:
let client = riverqueue::Client::builder(pool).build()?;client.insert(SortArgs { strings: vec!["whale".into(), "tiger".into(), "bear".into()] }).await?;A client created with only a driver can insert jobs without starting a worker runtime:
const client = new Client(driver);await client.insert(sort, { strings: ["whale", "tiger", "bear"] });Validating inserted jobs
Although not required, configuring Workers on an insert-only client lets it validate that inserted jobs have a registered worker. This catches misconfigurations early rather than at work time:
workers := river.NewWorkers()river.AddWorker(workers, &MyWorker{})
insertOnlyClient, err := river.NewClient(riverpgxv5.New(dbPool), &river.Config{ Workers: workers,})Worker registration is needed by worker processes. An insert-only Ruby client can serialize arguments and enqueue jobs without loading the destination worker classes. Keep the kind and JSON fields compatible with the worker that will receive them.
An insert-only Rust client can enqueue typed JobArgs without a worker registry. Use the same kind and serialized argument fields as the receiving workers.
defineJob provides the producer’s contract. A schema or decoder validates arguments at insertion, even without registering a worker:
const sort = defineJob({ kind: "sort", schema: z.object({ strings: z.array(z.string()) }),});
await client.insert(sort, { strings: ["whale", "tiger", "bear"] });What doesn’t run
Along with not working jobs, an insert-only client doesn’t participate in leader election, doesn’t run periodic jobs, and doesn’t execute any maintenance services. It wraps a database pool to make it possible to insert jobs, but does little else.