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

Hapi Laravel Package

mdbitz/hapi

Hapi is a Laravel package that streamlines API development with conventions and helpers for defining, organizing, and exposing HTTP endpoints, helping you build cleaner APIs with less boilerplate and tighter integration with Laravel’s routing and request lifecycle.

Deep Wiki
Context7

Getting Started

  1. Install and inspect before you integrate. Run composer require mdbitz/hapi, then immediately read the repository's README and src/ directory. With only ~19 stars, documentation is likely thin — the resource classes themselves are the real API reference. Identify the main entry point (typically a HaPi / Client class) and the per-resource classes (time entries, projects, clients, users, tasks). Pin the version explicitly in composer.json; an unmaintained package can shift between minor releases.

  2. Configure credentials in config/, never inline. Harvest needs an account subdomain (and, on API v2, an account ID) plus either a personal access token or Basic auth credentials. Put these in .env and expose them through a config/harvest.php file so php artisan config:cache doesn't null them out:

    // config/harvest.php
    return [
        'account_id' => env('HARVEST_ACCOUNT_ID'),
        'token'      => env('HARVEST_TOKEN'),
        'subdomain'  => env('HARVEST_SUBDOMAIN'),
        'user_agent' => env('HARVEST_USER_AGENT', 'MyApp ([email protected])'),
    ];
    
  3. Bind it once in a service provider. Register the client as a singleton in AppServiceProvider::register() (or a dedicated HarvestServiceProvider), injecting config values. Resolve it via the container everywhere else — never new HaPi(...) in a controller.

  4. First use case: read projects, write a time entry. The fastest way to validate credentials end-to-end is to (a) list active projects, (b) create one time entry against the first project, (c) fetch it back. That exercises auth, serialization, and the create/read paths you'll use daily.

Implementation Patterns

  • Wrap the client behind a gateway interface. Define App\Contracts\HarvestGateway with typed methods like projects(): Collection, createTimeEntry(TimeEntryData $dto): TimeEntry. Implement it with HaPi inside. This isolates the package so a v2 migration (or replacing it with raw Http:: calls) is a one-file change.
  • Sync reference data into your DB on a schedule. Clients, projects, tasks, and users change slowly. Build an artisan command (harvest:sync-reference) that iterates the package's list methods and upserts into local tables. Run it hourly or nightly; cache the results with Cache::remember for the API-call-heavy reads.
  • Push time entries through queued jobs. CreateHarvestTimeEntry job with $tries = 3, exponential backoff, and a unique key. Persist the Harvest ID back onto your local record so retries don't create duplicates.
  • Map API payloads to DTOs / local models. Don't let raw arrays from the package leak into controllers. A TimeEntryData DTO with spent_date, hours, project_id, task_id, notes keeps validation in one place and makes testing trivial.
  • Feature-test with a fake gateway. Bind a stub implementation of your interface in the test container. If the package uses Guzzle under the hood, Http::fake() won't intercept it — you'll need to swap the gateway or inject a Guzzle MockHandler.
  • Read-through caching for dashboards. Wrap projects(), clients(), and tasks() in Cache::remember('harvest.projects', now()->addMinutes(30), ...) and bust the cache inside your sync command.

Gotchas and Tips

  • API version mismatch is the #1 risk. Harvest retired API v1 (Basic auth, XML-ish payloads); v2 uses account IDs, bearer tokens, and JSON with Link-header pagination. Verify which the package targets before integrating — if it's v1-only, plan to wrap Illuminate\Support\Facades\Http directly for v2 and use the package only as a model/normalizer reference.
  • Rate limits. Harvest throttles per-user (roughly 100 requests / 15 seconds). Add ->retry(3, 1000, fn ($e) => $e->getCode() === 429) or a job middleware, and respect Retry-After. Bulk syncs should throttle deliberately, not hammer.
  • Paginate explicitly. Default page sizes vary by endpoint; don't assume one ->all() call returns everything. Loop on page / per_page and watch for repeated pages when records are mutated mid-sync.
  • Dates and hours are strings. Send spent_date as Y-m-d and hours as a decimal — never a Carbon object or a float with drift. Don't trust a Carbon::parse() round-trip; format on the way out and parse on the way in.
  • Inactive records still appear. Archived projects and inactive users are returned unless you filter on is_active. Filter in the sync or you'll recreate deleted local records.
  • Errors may be silent. Older wrappers sometimes return false or a generic exception on 4xx/5xx. Log the raw status code and response body before throwing; Harvest's error JSON includes a human-readable message you'll want in your report.
  • Config caching bites. Any env() call inside the package source will return null after php artisan config:cache. If credentials inexplicably break in production, that's the first thing to check.
  • Idempotency on retries. There's no client-supplied idempotency key in Harvest. Guard against duplicate time entries by recording the Harvest ID immediately and querying before creating in retries.
  • Extension points. Most wrappers of this era let you subclass the client or inject a Guzzle handler — use that to add request/response logging (huge for debugging auth issues) and to mock in tests.
  • License. GPL-3.0 is copyleft. Using it as a dependency in an internal Laravel app is fine; if you redistribute a modified version (or ship it inside a distributed product), you inherit source-availability obligations. Note it in your dependency review.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings