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

Laravel Apollo Laravel Package

jeffersongoncalves/laravel-apollo

Laravel Apollo is a Laravel package that integrates Apollo GraphQL, providing configuration and helpers to connect Apollo with Laravel applications for simpler GraphQL client setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up:

composer require <vendor>/laravel-apollo
php artisan vendor:publish --tag="apollo-config"

Add the credentials to .env — you will need an Apollo API key from Settings → Integrations → API in your Apollo account (a master key, not a user-scoped OAuth token):

APOLLO_API_KEY=your-key-here

Look at config/apollo.php first. That file is where the base URL, timeout, retry counts and any default pagination/rate-limit settings live; almost every "why is this slow / why did it 401" question starts there. Then open the service class behind the facade (typically src/Apollo.php or src/Client/…) — this package is thin enough that the method signatures are the documentation, and return types tell you whether you get arrays, collections or DTOs.

Your first use case should be the read-only one, because it costs no credits and validates the whole chain:

use Vendor\Apollo\Facades\Apollo;

$people = Apollo::people()
    ->search(['q_organization_domains' => 'acme.com', 'person_titles' => ['CTO']])
    ->get();

Confirm you get results and that Http::fake() in a test intercepts the call. Once search works, the enrichment endpoints (which do consume credits) are the same shape, so the plumbing is already proven.

Implementation Patterns

Search → enrich, not search-and-hope. Apollo's search response is deliberately thin: names, titles, company, and a has_email-style flag — not the email itself. The normal workflow is: search to get person_ids, persist those IDs, then enrich only the records you actually care about. Build this as two steps from day one rather than enriching inside a ->each() over search results, or you will burn credits on duplicates every time a job retries.

Wrap enrichment in queued jobs. Enrichment is network-bound and can take a second or more per call. A job per record (or per small batch) gives you Laravel's retry/backoff for free:

final class EnrichPerson implements ShouldQueue
{
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public string $personId) {}

    public function handle(Apollo $apollo): void
    {
        $person = $apollo->people()->enrich($this->personId);
        Prospect::where('apollo_id', $this->personId)->update([...]);
    }
}

Use Bus::batch() for bulk lists so you can hook then/catch and report partial failures — bulk enrichment of 500 people will rarely be all-or-nothing.

Bulk endpoints take an array; chunk it yourself. Bulk people enrichment accepts a list of identifiers, but the API caps how many per request. Don't pass an unbounded pluck(); wrap it in ->chunk(10) (verify the current cap in the README) and dispatch one job per chunk. This also keeps a single failed chunk from losing the whole batch.

Normalise into your own model immediately. Apollo's payloads are wide and will change. Map into an Eloquent model or a small DTO in the job, and never let raw Apollo arrays leak into Blade/Inertia/API responses. Keep apollo_id, apollo_raw (json column) and enriched_at on the record — you'll want to know when a record was fetched and be able to re-map without re-paying.

Cache aggressively for search, never for enrichment. Search results are cheap to re-fetch but you'll hammer them in UI autocompletes — cache for a few minutes keyed on the filter payload. Enrichment results are point-in-time and cost credits; store them in the DB with a timestamp instead of a TTL cache, so a cache flush doesn't silently trigger re-billing.

Testing. The package should go through Laravel's HTTP client, so Http::fake() works:

Http::fake(['api.apollo.io/*' => Http::response(['person' => [...]])]);

Fake at the HTTP layer rather than mocking the service class — that way the package's own request building and error mapping are still exercised in your suite.

Gotchas and Tips

  • Credits are the real cost, not latency. Every enrichment call bills. Retries, ->each() loops, cache misses and dev-environment seeders all spend real money. Set APOLLO_API_KEY to a throwaway key (or Http::fake()) in local and testing, and never let a seeder run against the live key. A Http::preventStrayRequests() in tests/TestCase.php is cheap insurance.
  • Search pagination is capped. Apollo limits how deep you can page through search results (roughly 500 pages / tens of thousands of records depending on plan). If you need a full TAM export, slice by domain or title and iterate filter variations, not page numbers — page: 600 will fail rather than return empty.
  • q_organization_domains wants a string, not an array. Several search params accept comma- or newline-separated values where you'd intuitively pass an array. Type errors here surface as silent zero-result responses, not exceptions — a great reason to assert count($results) > 0 in a smoke test.
  • Missing email ≠ error. Enrichment can succeed (HTTP 200, credits spent) and still return a record with no email because Apollo has no verified address. Treat nullable email as a normal outcome; queue a re-enrichment after N days rather than retrying immediately.
  • Rate limits are per-key and shared with anything else using that key. If your app and a CRM sync share one key, your retries will collide. Catch the 429, honour the Retry-After header, and set ->retry()/backoff in config/apollo.php conservatively — Laravel's default instant retry will make a 429 worse.
  • Timeouts default low in some Laravel HTTP configs. Enrichment occasionally takes >10s. Set an explicit timeout in the config (and a longer one on bulk calls) so you get a clean timeout error instead of a queued job that dies ambiguously.
  • Config is cached. If you change config/apollo.php or the env var inside a Docker build and nothing changes, it's php artisan config:cache — the classic first-hour debugging trap.
  • Error shape varies. Apollo returns errors as {"error": "..."}, as {"errors": [...]}, and sometimes as a 200 with an empty person array. Wrap the client once in a small ApolloException mapping layer so controllers and jobs don't each need to know the difference, and log the raw response body on failure — 4xx reasons are usually self-explanatory once you can see them.
  • Extension point: the HTTP client. Because everything funnels through one client, the cleanest place to add your own concerns — logging, metrics, credit accounting per team, a middleware that swaps keys for multi-tenant apps — is a Laravel HTTP middleware or a Guzzle HandlerStack bound into the container, not a fork of the package.
  • Laravel 12/13 + PHP 8.2 floor means the package can lean on DTOs, enums and readonly properties. If your app is still on PHP 8.1 you're locked out; check composer why-not before bumping the package in a legacy app.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource