idct/php-nats-jetstream-client
A PHP client for NATS JetStream, the persistent streaming layer of NATS. Publish and consume messages, manage streams and consumers, and handle acknowledgements from Laravel or plain PHP applications through a simple, idiomatic API.
Architecture fit
idct/php-nats-jetstream-client is a framework-agnostic, async-first PHP client for NATS Core, JetStream, KeyValue, ObjectStore, and the NATS service framework, built on PHP 8.2+ and Amp 3.x. The critical TPM observation is that this is not a Laravel package — it has no service provider, no facade, no config publishing, and no illuminate/* coupling. That is simultaneously its strength (no framework lock-in, no version-drift breakage on Laravel upgrades) and its cost (all Laravel glue is ours to write and own).
The bigger architectural tension is runtime model. Laravel's dominant execution model is short-lived, synchronous PHP-FPM requests. This library assumes a persistent event loop where blocking calls are forbidden. That means it only fits Laravel in three places: a supervised artisan daemon (consumer/worker), an Octane worker only if the loop is not shared with Octane's own loop, and CLI/batch tooling. It should not be introduced into the HTTP request path.
Where it genuinely earns its place in a Laravel estate: cross-service event fan-out where Redis pub/sub is too lossy and Kafka is too operationally heavy, JetStream KV as a low-latency config/feature-flag store, and request/reply for synchronous service-to-service calls.
Integration feasibility
Feasible, and the surface is small. Typical wrapper scope: a config/nats.php mapping env vars to ConnectionOptions, a singleton binding for NatsClient and JetStreamContext, a nats:consume command holding the Amp loop, and a thin bridge that maps decoded messages to Event::dispatch() or Bus::dispatch(). I would explicitly not build a custom Laravel Queue driver in phase one — JetStream's ack/nak/term and redelivery semantics do not map cleanly onto Laravel's job/retry/failed-job model, and pretending otherwise produces subtle duplicate-processing bugs.
Technical risk
sleep(), a synchronous HTTP client) inside a consumer stalls all consumers in that process. This is the most likely production incident and is a code-review discipline problem, not a library problem.AGENTS.md guidance is also unusually strong, which reduces agent-assisted onboarding friction.Key questions
Stack fit
Requires PHP 8.2+, which aligns with Laravel 11/12 baselines; no conflict. Amp 3.x brings its own dependency tree (amphp/socket, amphp/amp) — check for collisions with existing async dependencies (ReactPHP, Guzzle promises, Swoole extensions) before committing. Because the package has zero Laravel coupling, there is no version-matrix risk against illuminate/support, but also no upstream to fix things when Laravel changes.
Migration path
Position NATS as additive, not a replacement. The realistic path is: introduce NATS alongside Redis for new cross-service integration work, prove it on one bounded workflow, and only then consider migrating existing queue traffic. Do not open with "replace Horizon."
Compatibility
config/nats.php with env-driven credentials; support TLS and JWT/NKey from day one since the library's fixtures prove these paths.Sequencing
artisan nats:consume command against a local Docker NATS; publish from a Laravel job, consume and dispatch a Laravel event; verify reconnect behaviour by killing the server mid-consume.Maintenance
We inherit two maintenance surfaces: the package (pinned, vendored, reviewed on upgrade) and our Laravel adapter. The adapter is the larger long-term cost and should be treated as a first-class internal product with an owner, not a utility folder. Budget roughly 10–15% of one engineer's capacity for steady-state ownership plus NATS server operations.
Support
No commercial support, no SLA, no security disclosure process evident. Compensating controls: pin to a tag, run composer audit and the package's own static analysis in CI, and maintain a fork-ready posture. Escalation for a production defect is effectively "we fix it."
Scaling
Consumers scale horizontally using pull consumers with a shared durable name, so throughput scales with process count — but the real bottleneck will be downstream database writes, not NATS. Server-side, a 3-node NATS cluster with replication factor 3 gives the durability story that justifies choosing JetStream over Redis pub/sub. Watch for slow consumers and stream storage limits; JetStream will apply backpressure or drop, and that behaviour must be understood before launch.
Failure modes
| Failure | Customer-visible effect | Mitigation |
|---|---|---|
| Blocking call in consumer | All consumers in that process stall | Code review rule, lint for sleep/sync IO, per-process consumer isolation |
| Ack lost after processing | Duplicate handling (at-least-once) | Mandatory idempotency keys; JetStream dedupe window |
| Reconnect storm after NATS restart | Message backlog, latency spike | Library reconnect logic + alerting on reconnect count |
| Stale DB connections in long-running worker | Intermittent query failures | Connection refresh on interval; reconnect-on-error |
| Maintainer abandonment | No upstream fixes | Vendored fork, pinned version, upgrade ownership assigned |
| Credential/TLS expiry | Total connectivity loss | Rotation runbook tied to existing secret management |
Ramp-up
The library's own AGENTS.md is genuinely strong and will accelerate agent-assisted contribution, but the human learning curve is Amp's async model — futures, cancellation, cooperative yielding. Assume 1–2 weeks before an engineer is productive and 4–6 weeks before they are safe in production code. Two prerequisites before any build: a named owner, and an explicit written decision that we accept single-maintainer dependency risk in exchange for the opportunity gap this package fills (opportunity score ~60 vs. adoption score ~20 — the gap is real, but we would be an early adopter, and should price that in).
How can I help you explore Laravel packages today?