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

Nova Api Laravel Package

opscale-co/nova-api

A Laravel Nova package that adds a customizable REST API layer for your Nova resources, with authentication, filtering, sorting, pagination, and relationship support for exposing Nova-managed data to external apps.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require opscale-co/nova-api
php artisan vendor:publish --provider="Opscale\NovaApi\NovaApiServiceProvider"

If the provider name differs, run php artisan vendor:publish with no arguments and pick the nova-api entry from the list — that also tells you the exact config tag for future upgrades.

2. Read the config file first, in this order

The published config is the whole API surface of this package. Look at:

  • Route prefix / domain — decide immediately whether you want api/nova, api/v1/admin, or a separate subdomain. Changing this later means changing every client.
  • Middleware stack — this is where auth lives. The package ships token-based auth with configurable middleware; you almost certainly want to add throttle: and your own token-scoping middleware here.
  • Resource exposure list — the allow-list of Nova resources to expose. Start with one entry.

3. Confirm the Nova guard resolves a user

The package reuses Nova policies, which means the authenticated principal must be a user Nova can authorize against. Before writing any client code, prove that a token-authenticated request lands on a user whose policies actually run. A 403 on a resource you know is permitted usually means the guard is resolving null or the wrong model, not that the package is broken.

4. First use case: one read-only resource

Pick a low-risk, read-mostly resource — Ticket, Order, Invoice. Expose only index and show in config, point a token at it, and hit it:

curl -H "Authorization: Bearer $TOKEN" \
  "https://app.test/api/nova/tickets?page=1&perPage=25&search=acme"

Validate three things in that one response before going further:

  • Field selection matches what you expect from fieldsForIndex / fieldsForDetail (not fields()).
  • Relationship fields serialize in a shape your client can consume (IDs vs nested objects).
  • Pagination metadata is present and the page-size parameter name is what you think it is.

Once that round-trips cleanly, store/update on the same resource is a small increment; actions are the real second milestone.


Implementation Patterns

Opt-in exposure, not blanket exposure

Treat the config's resource list as an allow-list and grow it deliberately. The tempting shortcut — expose everything and rely on policies — means the shape of every Nova resource in the app becomes reachable HTTP surface, including ones with computed fields that do expensive work or leak derived data. Expose per resource, per verb.

Let Nova be the contract

The package's value proposition is that Nova's field, filter, and policy metadata are the API definition. Lean into that rather than fighting it:

  • Authorization stays in policies. Don't duplicate permission logic in middleware. If a partner should only see their own orders, that belongs in the OrderPolicy, where Nova staff already benefit from it. Note the method-name mapping — viewAny gates index, view gates show, create gates store, update gates update, delete gates destroy.
  • Serialization stays in fields(). Use onlyOnIndex / onlyOnDetail and hideFromIndex / hideFromDetail to shape list vs. detail payloads. This is the intended lever for payload shaping — reach for it before reaching for custom controllers.
  • Filters stay in Nova filters. Nova filters are already wired to query params; the work is mapping your client's filter vocabulary onto the filter class names and argument shapes the package expects.

Token scoping middleware

A single token type with full Nova access is fine for an internal service and wrong for a partner. Insert a middleware after auth that narrows what the token can touch:

// config/nova-api.php (shape varies — check the published file)
'middleware' => [
    'api',
    'auth:nova-api',
    \App\Http\Middleware\ScopeNovaApiToken::class,
    'throttle:60,1',
],

Have that middleware resolve the token's allowed resource list and reject mismatches with 403 before the policy layer runs. This keeps the config allow-list as the global ceiling and the token as the per-client ceiling — two independent gates, both auditable.

Actions as a workflow surface

v1.3.0 bumps opscale-co/actions to ^4.0, which confirms action invocation is a first-class part of this package, not an afterthought. The pattern that pays off:

  1. Define the workflow once as a Nova action (approve, reconcile, resend, sync).
  2. Staff trigger it from the Nova UI; partners trigger it over the API.
  3. Audit logging happens in the action's handle(), so both paths are covered by one implementation.

This is the single biggest "weeks not quarters" win in the package — it's the difference between "we built an API" and "we exposed a workflow." Do a regression pass over every action endpoint when you bump the dependency, and read the upstream changelog for opscale-co/actions before you do.

Integration tips

  • Put an API gateway or at least throttle in front. Rate limiting isn't the package's job. Nothing here will stop a partner's retry loop.
  • Version at the prefix, not the payload. /api/v1/nova/... costs nothing today and buys you a migration path when a Nova field rename silently changes your response shape.
  • Test with HTTP tests, not unit tests. The whole surface is middleware → policy → field resolution. $this->withToken($token)->getJson(...) catches guard misconfiguration that a direct controller test never will.
  • Keep a fixture test per exposed resource that asserts the JSON structure (assertJsonStructure) for both index and show. This is your only early warning when someone edits fields() for a UI reason and breaks a partner integration.

Gotchas and Tips

Relationship fields are the usual first surprise

BelongsTo, HasMany, and BelongsToMany fields behave differently in an API context than in the Nova UI, where the front-end does extra work. Expect IDs or stubbed objects where you wanted nested data, and resolveUsing closures that assume $this->resource is fully loaded. Decide explicitly per relationship: nested payload (via a computed field or a resource-level resolver) or ID + separate lookup endpoint. Don't discover this in production.

Computed fields run — including the expensive ones

A Text::make('Lifetime Value', fn () => $this->calculateLtv()) that's tolerable on a 25-row Nova page becomes a latency cliff on a paginated API hit by a client polling every 30 seconds. Anything with a query inside or an external call should be onlyOnDetail or excluded from the API entirely.

File and image fields need URL context

If your app serves assets from a non-default disk or behind a CDN/signed URLs, field URLs may come back wrong or unsigned in API responses. Check one response per file-backed resource in a non-local environment, not just on your machine.

403 vs 404 will cost you an afternoon

Nova's authorization habits mean a missing policy method can surface as a 403 rather than a 404, and a misconfigured guard surfaces as a 403 on everything. When debugging, work outward: is there a user? does the policy method exist? does the policy return true for this user? Assert on that chain with Nova::actingAs() in a test before blaming the package.

Validation rules are context-dependent

Nova fields carry rules, but also creationRules and updateRules. Confirm which set store and update actually apply — if the package leans on the field's base rules, your unique-email-ignore-self constraint on update may not fire. Test an update that should fail and make sure it does.

Route caching and config changes

Routes here are config-driven, so they participate in php artisan route:cache like any others. Changing the exposure list or prefix in production requires a route cache rebuild — and if you're mid-deploy with config:cache on, a stale cached config is a very plausible explanation for "the new endpoint 404s."

Middleware ordering and double auth

If your app already uses auth:sanctum or auth:api globally on the api group, stacking the package's token middleware on top can produce confusing "authenticated but unauthorized" states. Verify which guard actually wins, and keep the package's auth config to exactly one guard.

Search is bounded by searchable

?search= only queries columns listed in the resource's $searchable array. If a client says search "doesn't work" on a field, it's almost always a missing $searchable entry, not a package bug. Same class of issue for filters: a filter that works in the Nova UI may expect an argument shape the query string can't express cleanly — document the exact param shape per filter for your clients.

Extension points, in order of preference

  1. Config — prefix, middleware, exposure. Use this first.
  2. Middleware — token scoping, logging, rate limits, response header injection. Cheap, testable, survives upgrades.
  3. Nova field configuration — onlyOnIndex, resolveUsing, computed fields. Still declarative, still one source of truth.
  4. Custom controllers — a last resort, because every one you write is a place the API and the admin panel can drift apart.

The maintenance stance to take

This is a low-star package whose most recent release is a single-line transitive major bump (opscale-co/actions to ^4.0) with no migration notes. For something sitting in your request path:

  • Pin the version in composer.json rather than floating, and read the diff before bumping.
  • Keep the fixture tests described above — they're your cheap regression suite for exactly this kind of dependency churn.
  • Run a spike covering action-endpoint behavior after any bump, not before.
  • Treat the generated payload as your contract even though you don't own the generator: if a Nova field change can break a partner, you need a test that fails loudly, because the package won't warn you.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons