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

Nitro Php Bridge Laravel Package

flyo/nitro-php-bridge

A lightweight PHP bridge for Flyo Nitro that lets Laravel and other PHP applications connect to the Nitro content API, fetch structured headless CMS content, and integrate it cleanly into templates, pages, and custom applications.

View on GitHub
Deep Wiki
Context7

Getting Started

What this package is (and isn't). flyo/nitro-php-bridge is the transport/adapter layer between Flyo Cloud (the headless CMS) and a PHP backend. It is deliberately thin: authentication, HTTP calls against the Nitro API, and decoding responses into PHP structures. It is not a full CMS integration — routing, caching, view models, preview handling and error semantics are your job. Treat it as "an authenticated HTTP client with Flyo's schema baked in."

Minimal setup

composer require flyo/nitro-php-bridge

Then, in a Laravel app:

  1. Add credentials to .env — typically a space/project identifier plus an API token (and a separate preview token if the Cloud project exposes drafts):
FLYO_NITRO_TOKEN=...
FLYO_NITRO_SPACE=...
FLYO_NITRO_BASE_URI=https://api.flyo.cloud   # confirm against README
  1. Publish/author the config file (config/nitro.php or similar) and only read env there, never in application code — otherwise php artisan config:cache will silently produce null tokens in production.
  2. Bind the client as a singleton in a service provider so you get connection reuse and a single place to swap the HTTP client:
$this->app->singleton(NitroClient::class, fn ($app) => new NitroClient(
    token: config('nitro.token'),
    baseUri: config('nitro.base_uri'),
));

Where to look first, in this order

  • README.md — the only reliable source for constructor signature and endpoint helpers.
  • src/ root — the client class(es); skim public methods to see what endpoints are actually wrapped versus what you must call by hand.
  • config/ and any *ServiceProvider — tells you whether it's Laravel-aware or framework-agnostic (the -php-bridge suffix suggests the latter, with framework packages layered on top).
  • tests/ — the fastest way to learn intended usage; fixtures usually double as response-shape documentation.
  • CHANGELOG / git tags — this package has essentially no public adoption, so version drift is your risk, not the community's.

First use case to implement. The canonical flow is: a catch-all route resolves a slug → the bridge fetches the corresponding page/entity → you map it to a view model → render. Build that one vertical slice end-to-end before wiring anything else; it forces you to confront response shape, 404 semantics and caching immediately, which are the three things that will bite later.

Implementation Patterns

Wrap it. Don't call it from controllers directly. Even at 0 stars, the API surface of a bridge package is the most likely thing to change under you. Introduce one application-level seam:

interface ContentSource
{
    public function page(string $slug, ?string $locale = null): ?PageData;
    public function collection(string $handle, array $params = []): Collection;
}

Implement it with the bridge, and inject ContentSource everywhere. Swapping to the raw Nitro API, a cache-backed decorator, or a different CMS then becomes a one-line container change.

Catch-all content routing. One route, one controller, resolver decides what to render:

Route::get('/{path?}', ContentController::class)->where('path', '.*');

The controller asks ContentSource for a page by path; null → abort(404). Keep the resolver dumb — no template-specific branching inside it. Map the returned structure into a typed view model (PageData, BlockData) so Blade views never touch raw bridge arrays. This is the single highest-value habit with headless CMS adapters: the raw response shape leaks into 40 templates and then you can never change it.

Caching belongs to you. The bridge will hit the network on every call. Wrap ContentSource in a decorator:

class CachedContentSource implements ContentSource
{
    public function page(string $slug, ?string $locale = null): ?PageData
    {
        return Cache::remember(
            "nitro:page:{$locale}:{$slug}",
            now()->addMinutes(10),
            fn () => $this->inner->page($slug, $locale),
        );
    }
}

Bind the decorator in the container, keep the inner implementation private, and expose a small nitro:flush artisan command (or listen for a Flyo webhook) to invalidate. Never cache null/404s for long — content that was just published will appear broken for the TTL duration.

Preview / draft mode. Model this as a second client instance rather than a boolean flag threaded through everything:

$this->app->singleton(ContentSource::class, fn ($app) => $app->bound('request') && $app['request']->hasValidPreviewCookie()
    ? new PreviewContentSource($app->make(PreviewNitroClient::class))
    : new CachedContentSource($app->make(NitroContentSource::class)));

Preview bypasses the cache and uses the draft token; the public path never sees draft content. Gate the preview cookie behind auth middleware — a leaked preview token exposes unpublished content.

Batch and expand, don't loop. Remote CMS APIs are the classic N+1 trap. If the Nitro response supports relation expansion (?expand=/?include= style), use it. Iterating a list of 30 items and calling the bridge 30 times to resolve relationships will dominate your request time and trip rate limits.

Error mapping. Translate at the boundary: bridge/HTTP 404 → ModelNotFoundException (so Laravel renders 404), 5xx/timeouts → ContentUnavailableException → 503 with a cached fallback if you have one. Don't let a Guzzle exception surface from a controller.

Testing. Because you introduced ContentSource, tests bind a fake and never hit the network:

$this->swap(ContentSource::class, new InMemoryContentSource([...]));

Record one real response per content type as a fixture and let a single integration test (marked @group external) assert the bridge still parses it. That test is your early-warning system for upstream schema changes.

Locale handling. If the Cloud project is multi-locale, resolve locale once in middleware and pass it explicitly into ContentSource — never read app()->getLocale() deep inside the adapter, or cache keys and content will drift apart.

Gotchas and Tips

  • Assume zero community support. No stars, no dependents, no Stack Overflow corpus. Pin the exact version ("flyo/nitro-php-bridge": "1.2.*", not ^1.2) and keep it behind your ContentSource seam. Budget for reading src/ when something breaks, because there's nobody else who has.
  • Verify installability first. The last release is dated 2026-08-06 — check that the tag actually resolves before you build on it (composer show flyo/nitro-php-bridge --all). If it doesn't, you'll be on dev-main, which is a materially different risk posture and should be documented in your README.
  • Config caching bites. env() calls outside config/ return null once config:cache runs. If content mysteriously 401s in staging but works locally, this is the cause 90% of the time.
  • Silent null vs. exceptions. Adapters in this genre often return null/empty arrays on failure rather than throwing. Assert explicitly on the response shape in your adapter layer, or you'll ship a blank page instead of an alert.
  • Timeouts and retries are not pre-configured. Set connect/read timeouts (2s/5s is a reasonable start) and retry only idempotent GETs, with backoff. A hanging CMS call should degrade one page, not exhaust your PHP-FPM workers.
  • Debugging: log the raw payload, not the mapped object. Add a local-only middleware or config flag that dumps the decoded response and any request/trace ID header the API returns. When a block renders empty, the answer is nearly always "the field name in the response differs from what you assumed," and the raw dump answers it in seconds.
  • Extension points worth using. Rather than forking: inject a configured Guzzle client (custom middleware for logging, auth refresh, correlation IDs), and if the package exposes response transformers or events, hook there. If it accepts a PSR-18 client, you get caching and instrumentation for free without touching the package.
  • Don't cache the client's decoded objects across requests unless they're plain scalars/arrays — serialized DTOs from third-party packages are the most common source of "it works until a minor version bump" failures.
  • Guard against content-shape drift. Add a nullable-tolerant mapping layer with sensible defaults for optional blocks, so a new field appearing upstream (or a field being emptied by an editor) doesn't throw a TypeError on a production page.
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.
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus