Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzle Rate Limiter Middleware Laravel Package

spatie/guzzle-rate-limiter-middleware

A Guzzle middleware that enforces rate limits by specifying max requests per minute or second. When the limit is reached, requests sleep until allowed. Includes an InMemoryStore and supports custom persistent stores for cross-process rate limiting.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

Partner/API quota compliance as a shipping gate. Any integration with a documented "N requests per minute" contract is a hard product constraint, not an implementation detail. This package turns that contract into one line of configuration (RateLimiterMiddleware::perMinute(60)) sitting next to the credentials in config/services.php, which means the limit becomes reviewable in a PR instead of tribal knowledge. That directly supports roadmap decisions like "can we ship the bulk sync this quarter" — the answer usually depends on whether you can stay inside a quota, not on whether you can write the loop.

Build vs. buy: buy, but narrowly. The logic is small enough to write in an afternoon, but the two things you'd get wrong are the ones that bite in production: the pluggable Store interface that lets the request history survive across processes, and the Deferrer seam that lets you swap usleep for something observable. Reimplementing costs a sprint of engineering plus a permanent maintenance line; adopting costs a Composer dependency, MIT-licensed, with no config files and no service provider to own. At 169 stars and zero dependents it is a tactical utility, not a platform bet — treat it as such in the buy/build ledger.

Architecture decisions about where outbound calls are allowed to block. The package's core behavior is sleep in the PHP process. That is a fine trade in a queue worker and an unacceptable one in a synchronous request behind a user's spinner. Forcing that distinction into the open early shapes real product choices: which operations are async-only, whether a "sync now" button exists at all, and how much latency budget a given feature gets. It's the kind of decision that's cheap before the first endpoint ships and expensive after.

Multi-worker scaling math. The bundled InMemoryStore gives every PHP process its own full quota — four queue workers means four times the limit, which means you trip the partner's limiter anyway and the tool silently failed. The decision this forces is infrastructural: Redis- or DB-backed Store before horizontal scaling, not after. That's a capacity-planning input, not a code detail.

Retry and backoff strategy as a paired decision. Pacing prevents 429s; it does nothing about the one that arrives anyway from clock skew or another consumer sharing the API key. So this package is only half a reliability story — it should be adopted alongside retry middleware, and the limit sized with retries in mind (a 429 storm drains the pacing budget faster than steady-state traffic). This is a "define the whole policy" decision rather than a "add a library" decision.

Use cases worth scoping: queued jobs and artisan commands pulling from partner APIs; scheduled syncs against rate-limited SaaS endpoints (CRM, ERP, payment, logistics); webhook fan-out where the outbound side has a cap; multi-tenant background processing where one tenant's burst must not starve another's quota.

Instrumentation as a first-class requirement. There is no event and no log line when the limiter sleeps, and silent multi-second stalls are precisely what you want on a dashboard. The Deferrer interface (v2) is the only hook. If you adopt this, budget for a decorating Deferrer that emits a metric — otherwise the first "why is the nightly sync slow" investigation starts from zero.

When to Consider This Package

Good fit when:

  • An external API publishes an explicit, static rate limit and enforces it with throttling or bans.
  • The call path is a background worker, cron job, or CLI command where blocking costs latency rather than user experience.
  • You already use Guzzle directly, or use Laravel's Http facade in a way that lets you inject a HandlerStack.
  • You can commit to a shared Store (Redis/DB) because you run more than one process. If you can't, don't adopt — you'll get the illusion of protection without the substance.
  • The quota is uniform per host/credential. This package has no per-endpoint or per-user differentiation; /search at 5/min and /bulk at 1/min means two clients, two stacks, two store keys. If your quota model is granular, you're building a layer above this regardless.

Look elsewhere when:

  • The limit is dynamic or discoverable at runtime — i.e. you should honor X-RateLimit-Remaining / Retry-After headers. This package is a fixed local budget; it does not read response headers. Header-driven throttling is a different, and often better, design for well-behaved APIs.
  • You cannot block. Synchronous web requests, streaming responses, or anything with a sub-second SLA. A usleep in an FPM worker is a queue of stalled workers; prefer a token-bucket check that fails fast plus a queued retry.
  • You need cross-service coordination at high volume — this is a per-client budget, not a distributed quota service.
  • You need observability out of the box. You will build the metrics yourself.
  • You're pre-Guzzle 6.3 or below PHP 8.3. Hard floor, no polyfill path.
  • You need a maintained-by-committee dependency. Single-package, small-maintainer-surface OSS. Recent releases (PHP 8.4 and Guzzle 8 support landing in 2.1/2.2) are a good sign of health, but the risk profile is "one person's side project," not "vendor with an SLA." For a critical integration, keep a fork-or-replace path in mind.

Adoption caveats to write into the ticket: pin ^2.2 if you're on Guzzle 8 — earlier versions type-error against it. Bind the client (or HandlerStack) as a singleton so the store and its history survive across calls in a worker; scattering HandlerStack::create() across the codebase silently multiplies your quota. Order matters with retries: the limiter must sit inside the retry middleware so every attempt counts.

How to Pitch It (Stakeholders)

To executives — one paragraph: This is a small, free (MIT), well-maintained library from Spatie that stops our outbound API calls from exceeding partner-imposed request quotas. Without it, a burst of background traffic can get our integration throttled or our API key suspended — a customer-visible outage with an external cause and a slow remediation path. With it, the quota lives in config next to the credentials, is reviewable in code review, and enforcement is automatic. The cost is a single Composer dependency and roughly a day of engineering to wire correctly, including a shared cache so it works across our queue workers. It is a tactical reliability control, not a strategic platform commitment.

To engineering — the shape of the work: One configured Guzzle client per external API, bound as a singleton in the service container, with the limit read from config/services.php. Implement the two-method Store interface against Redis or the cache before we scale past one worker — the bundled in-memory store gives each process its own full quota, so N workers means no limiting at all. Prune the timestamp array in push() using the $limit argument (that's why v2 added it) and set a TTL. Pair it with retry middleware for 429/503, ordered so the limiter sits inside the retry so every attempt is counted. Decorate SleepDeferrer to log and emit a metric, because the package emits nothing on its own. Scope it to queued jobs and console commands only — no blocking sleeps in web request paths. Where an API exposes Retry-After or X-RateLimit-Remaining, treat that as the authority and this as the floor.

The honest caveat to state up front: this is a 169-star utility, not infrastructure with a support contract. It solves one problem well — fixed local pacing for Guzzle clients — and deliberately solves nothing else. No header awareness, no per-endpoint quotas, no observability. Value lands only if we do the store, the ordering, and the instrumentation around it; the library itself is the smallest part of the adoption.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib