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

Audit Api Laravel Package

liberusoftware/audit-api

A lightweight Laravel package for exposing audit trail data through a clean, consistent API. Query and surface audit records for your application, supporting compliance, activity tracking, and change history with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

liberusoftware/audit-api is a presentation adapter — the HTTP layer of a larger audit subsystem in the Liberu modular stack. It does not own the audit domain (models, migrations, write paths); it exposes routes, controllers, request validation, and API resources on top of a domain package. Read it as "the REST skin over the audit log," not "an audit log you install and use."

Prerequisites and install

  • PHP 8.2+, Laravel 10/11+, and the sibling domain module it adapts (check composer.json require/suggest — this package is useless without it).
  • composer require liberusoftware/audit-api
  • Because it is a presentation layer, expect the ServiceProvider to register routes and bindings only. Migrations and config most likely live in the domain package; run php artisan vendor:publish --provider="...AuditApiServiceProvider" and inspect what actually appears before assuming.
  • Verify it loaded: php artisan route:list --path=api/audit

Where to look first, in order

  1. The package's src/Providers/*ServiceProvider.php — reveals route registration, middleware group, prefix, and any container bindings. This is the map of the whole package.
  2. routes/api.php (or routes/*.php) — the actual surface area. Usually a handful of read endpoints: list, show, and possibly export/stream.
  3. src/Http/Resources/ — the JSON contract. This is what your frontend, SPA, or mobile client will be coded against, so treat these classes as the real API documentation.
  4. The domain package's contracts/query objects — the adapter almost certainly delegates to them, so the interesting behavior (filtering semantics, retention, redaction) lives there.

First use case

Give an admin/compliance screen a filterable, paginated audit trail for a single record:

curl -H "Authorization: Bearer $TOKEN" \
  "https://app.test/api/audit/logs?filter[subject_type]=App\\Models\\Order&filter[subject_id]=42&sort=-created_at&per_page=50"

Then wrap it in a thin Laravel client so your app never hardcodes URLs:

// config/services.php
'audit' => ['base' => env('AUDIT_API_BASE', config('app.url').'/api/audit')],

Http::baseUrl(config('services.audit.base'))
    ->withToken(config('services.audit.token'))
    ->acceptJson()
    ->get('logs', ['filter' => ['subject_id' => $order->id], 'per_page' => 50])
    ->throw()
    ->json('data');

If you are calling it server-side inside the same monolith, skip HTTP entirely and resolve the query handler from the container — see the integration tip below.

Implementation Patterns

Keep the adapter an edge, not a source of truth. The healthiest usage is: your application writes audit records through domain events (or the domain package's recorder/logger service), and reads them back through this adapter's endpoints. If you find yourself POSTing audit entries from your app code, check first whether an ingestion route even exists — in most of these presentation adapters it does not, and audit writes are deliberately non-HTTP.

// Write side: do this, via the domain module's recorder or an event listener.
Event::listen(OrderShipped::class, RecordAuditEntry::class);

// Read side: the adapter's endpoint, or the domain query object in-process.
$entries = app(QueryAuditLog::class)->handle(new AuditLogFilter(
    subjectType: Order::class,
    subjectId: $order->id,
    from: now()->subDays(30),
));

Authorize at the route, filter at the query. Audit data is the most sensitive table in the app — it contains before/after payloads, IPs, user agents. Do not rely on the package's defaults:

// AppServiceProvider::boot() or a dedicated middleware group
Gate::define('view-audit', fn (User $u) => $u->hasRole('compliance'));

// If the package exposes a config for middleware, use it rather than
// re-registering routes — you will lose upstream fixes otherwise.
'audit-api' => [
    'middleware' => ['api', 'auth:sanctum', 'can:view-audit', 'throttle:60,1'],
],

If no such config key exists, register the middleware by extending the package's route group in your own RouteServiceProvider after the package's, or by publishing and editing the routes file — prefer the former so upgrades stay cheap.

Treat resources as a versioned contract. Snapshot the JSON shape in a test before you build a UI on it:

it('keeps the audit log contract stable', function () {
    $this->actingAs(User::factory()->compliance()->create())
        ->getJson('/api/audit/logs?per_page=1')
        ->assertOk()
        ->assertJsonStructure([
            'data' => [['id', 'event', 'subject_type', 'subject_id', 'created_at', 'changes']],
            'links', 'meta',
        ]);
});

This is the single highest-value test you can add for a 0-star dependency: it turns a silent breaking change on composer update into a red build.

Consume it consistently from the front end. Map resource fields once, in one place, rather than in every component:

const toTimelineItem = (row) => ({
  at: row.created_at,
  actor: row.causer?.name ?? 'system',
  summary: `${row.event} on ${row.subject_type.split('\\').pop()} #${row.subject_id}`,
  diff: row.changes,
});

Integration tips

  • Bind the package's interfaces in your app service provider if the adapter ships contracts — that keeps your code depending on interfaces rather than the vendor namespace, and makes faking it in tests trivial.
  • For dashboards, ask for aggregates rather than pulling raw rows: either use a filter the domain query supports, or maintain a read model. Pulling 100k audit rows into PHP to count() them is the classic mistake here.
  • Use Http::fake() with a recorded JSON fixture in your own app's tests so your test suite doesn't depend on the domain module being booted.

Gotchas and Tips

Adoption and stability are the real risk. 0 stars, 0 dependents, and a release date that reads as future-dated (2026-10-26) is a signal to pin hard and audit before you ship:

{ "require": { "liberusoftware/audit-api": "1.2.*" } }

Never let a dev-main constraint resolve into production; a presentation adapter changing resource keys is a breaking change you cannot see in your own diff.

License is NOASSERTION. Composer will install it happily; your legal/OSS policy probably won't. Resolve this before it becomes a release blocker — check the repo for a LICENSE file versus missing metadata in composer.json. For internal/ERP use this is often fine, but it is not automatically fine.

Missing domain module = confusing 500s, not a clear error. A presentation adapter that can't resolve its query handler will fail deep in the container with a Target [..Contract] is not instantiable or a "table not found" from a migration that never ran. When something breaks, check in this order: is the domain package installed, are its migrations run, is the config published, are routes registered.

Route and config caching bite harder here. php artisan route:cache on a package that registers routes conditionally (feature flags, if (config('audit-api.enabled'))) silently drops them. Same for config:cache with env() calls inside the package's config file — those evaluate once and freeze. After any deploy with caching enabled, run route:list in the deployed environment.

Auth guard mismatches. Adapters typically register under the api middleware group with a sanctum or passport guard. Session-authenticated SPA calls will 401 with no useful message. If you're using the web guard, either move the routes or add the session middleware explicitly.

Performance and indexing. Audit tables grow fastest of anything in an ERP. Confirm compound indexes exist on (subject_type, subject_id, created_at) and (causer_type, causer_id) before you let a UI sort by created_at across the whole table. Watch for N+1s on causer/subject eager-loading, and cap per_page in your middleware or config — an unbounded per_page=100000 is a trivial DoS from inside your own app.

Date filtering and timezones. Date range filters in audit APIs are frequently applied against a UTC column while the UI sends local time, shifting results by hours at the boundary. Send ISO-8601 with offset, and write a test that asserts an entry created at 23:30 local appears in "today."

Redaction is your problem, not the package's. Audit changes payloads capture whatever was in the model — password hashes, tokens, PII. Ensure the domain module's $auditExclude/$auditHidden configuration covers sensitive attributes, and add a test that asserts a password change is recorded as [REDACTED]. Do not assume the adapter strips it.

Extension points to prefer, in order: config overrides → middleware addition → container rebinding of query/resource classes → publishing and editing resources → forking. Every step down that list costs more on upgrade. If you must fork, do it as a thin subclass in your own namespace so you can drop it when upstream catches up.

Debugging toolkit. php artisan route:list --path=audit -v for middleware, php artisan tinker to resolve the query handler directly and see whether the bug is in the adapter or the domain, Laravel Telescope for the actual SQL and bindings, and Log::info() inside your published copy of the resource if the JSON shape is surprising. When reporting an issue upstream, include the resolved route middleware and the composer show output — for a package this small, maintainers will need it.

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