PHPackages                             carlin/laravel-api-relations - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. carlin/laravel-api-relations

ActiveLibrary[Database &amp; ORM](/categories/database)

carlin/laravel-api-relations
============================

Eloquent-like API relationships for Laravel with composite key support and N+1 prevention

v1.1.1(5mo ago)11MITPHPPHP ^8.1

Since Dec 11Pushed 5mo agoCompare

[ Source](https://github.com/carlin-rj/laravel-api-relations)[ Packagist](https://packagist.org/packages/carlin/laravel-api-relations)[ RSS](/packages/carlin-laravel-api-relations/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (1)Dependencies (4)Versions (4)Used By (0)

Laravel API Relations
=====================

[](#laravel-api-relations)

[![Latest Version](https://camo.githubusercontent.com/5dab620a29d81e358b4df4cf5e6d5eddd9b483b87924937d2da877801dc19e11/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6361726c696e2f6c61726176656c2d6170692d72656c6174696f6e732e737667)](https://packagist.org/packages/carlin/laravel-api-relations)[![License](https://camo.githubusercontent.com/5d92ff8eeee140c87cbc482907e55040b507d25629cf8c7097eb93d35383549d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6361726c696e2f6c61726176656c2d6170692d72656c6174696f6e732e737667)](https://packagist.org/packages/carlin/laravel-api-relations)[![PHP Version](https://camo.githubusercontent.com/6e6917f10e111f969b97ac362b0e23befa1a0fc20fa931701ed684f5fcde420a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6361726c696e2f6c61726176656c2d6170692d72656c6174696f6e732e737667)](https://packagist.org/packages/carlin/laravel-api-relations)

Eloquent-like API relationships for Laravel with composite key support and N+1 prevention through intelligent batch loading.

English | [简体中文](README_CN.md)

Features
--------

[](#features)

- 🚀 **Eloquent-like syntax** - Define API relationships just like database relationships
- 🔑 **Composite key support** - Handle complex relationships with multiple keys
- ⚡ **N+1 prevention** - Automatic batch loading for optimal performance
- 🎯 **Lazy &amp; Eager loading** - Full support for both loading strategies
- 🔤 **Case-insensitive matching** - Optional case-insensitive key matching for flexible API integration

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- Laravel 8.x, 9.x, or 10.x

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require carlin/laravel-api-relations
```

Why This Package?
-----------------

[](#why-this-package)

### Before: The Traditional Approach ❌

[](#before-the-traditional-approach-)

Without this package, you typically need Service classes to fetch and attach API data:

```
// UserService.php
class UserService
{
    public function getUserWithProfile($userId)
    {
        $user = User::find($userId);

        // Fetch profile from external API
        $response = Http::post('https://api.example.com/profiles', [
            'user_ids' => [$userId]
        ]);
        $profiles = $response->json();
        $user->profile = $profiles[0] ?? null;

        return $user;
    }

    public function getUsersWithProfiles($userIds)
    {
        $users = User::whereIn('id', $userIds)->get();

        // Batch fetch to avoid N+1
        $response = Http::post('https://api.example.com/profiles', [
            'user_ids' => $userIds
        ]);
        $profiles = collect($response->json())->keyBy('user_id');

        // Manually attach profiles to users
        foreach ($users as $user) {
            $user->profile = $profiles->get($user->id);
        }

        return $users;
    }
}

// Controller usage
class UserController extends Controller
{
    public function index(UserService $userService)
    {
        // Must remember to use the service method
        $users = $userService->getUsersWithProfiles([1, 2, 3]);

        return view('users.index', compact('users'));
    }

    public function show($id, UserService $userService)
    {
        // Different method for single user
        $user = $userService->getUserWithProfile($id);

        return view('users.show', compact('user'));
    }
}
```

**Problems:**

- 🔴 Need separate Service classes for API data fetching
- 🔴 Controllers must remember to use specific service methods
- 🔴 Different methods for single vs. multiple records
- 🔴 Manual data attachment in every service method
- 🔴 Easy to forget batch loading, causing N+1 problems
- 🔴 Can't use Eloquent's `with()` for eager loading
- 🔴 Breaking Eloquent conventions and patterns

### After: With Laravel API Relations ✅

[](#after-with-laravel-api-relations-)

API relationships work exactly like Eloquent relationships:

```
// User.php
class User extends Model
{
    use HasApiRelations;

    public function profile()
    {
        return $this->hasOneApi(
            callback: fn($userIds) => Http::post('https://api.example.com/profiles', [
                'user_ids' => $userIds
            ])->json(),
            foreignKey: 'user_id',
            localKey: 'id'
        );
    }

    public function posts()
    {
        return $this->hasManyApi(
            callback: fn($userIds) => Http::post('https://api.example.com/posts', [
                'user_ids' => $userIds
            ])->json(),
            foreignKey: 'user_id',
            localKey: 'id'
        );
    }
}

// Controller usage - just like regular Eloquent!
class UserController extends Controller
{
    public function index()
    {
        // Automatic batch loading - single API call for all users
        $users = User::with('profile', 'posts')->get();

        return view('users.index', compact('users'));
    }

    public function show($id)
    {
        // Lazy loading - works seamlessly
        $user = User::find($id);
        $profile = $user->profile()->getResults();

        return view('users.show', compact('user', 'profile'));
    }
}
```

**Benefits:**

- ✅ No service layer needed for API relationships
- ✅ Use standard Eloquent `with()` for eager loading
- ✅ Automatic N+1 prevention through intelligent batching
- ✅ Consistent API with database relationships
- ✅ Single model definition works for all scenarios
- ✅ Controllers stay clean and follow Laravel conventions
- ✅ Built-in composite key support

---

Quick Start
-----------

[](#quick-start)

### 1. Add the Trait to Your Model

[](#1-add-the-trait-to-your-model)

```
use Carlin\LaravelApiRelations\Traits\HasApiRelations;

class User extends Model
{
    use HasApiRelations;

    // Define a has-one API relationship
    public function profile()
    {
        return $this->hasOneApi(
            callback: fn($userIds) => $this->fetchProfilesFromApi($userIds),
            foreignKey: 'user_id',
            localKey: 'id'
        );
    }

    // Define a has-many API relationship
    public function posts()
    {
        return $this->hasManyApi(
            callback: fn($userIds) => $this->fetchPostsFromApi($userIds),
            foreignKey: 'user_id',
            localKey: 'id'
        );
    }

    private function fetchProfilesFromApi(array $userIds): array
    {
        // Call your external API
        $response = Http::post('https://api.example.com/profiles', [
            'user_ids' => $userIds
        ]);

        return $response->json();
    }

    private function fetchPostsFromApi(array $userIds): array
    {
        // Call your external API
        $response = Http::post('https://api.example.com/posts', [
            'user_ids' => $userIds
        ]);

        return $response->json();
    }
}
```

### 2. Use Like Regular Eloquent Relationships

[](#2-use-like-regular-eloquent-relationships)

```
// Lazy loading (single API call per model)
$user = User::find(1);
$profile = $user->profile()->getResults();
$posts = $user->posts()->getResults();

// Eager loading (single batched API call for all models)
$users = User::with('profile', 'posts')->get();

foreach ($users as $user) {
    echo $user->profile['name'];
    foreach ($user->posts as $post) {
        echo $post['title'];
    }
}
```

Advanced Usage
--------------

[](#advanced-usage)

### Case-Insensitive Key Matching

[](#case-insensitive-key-matching)

By default, key matching is case-sensitive. You can enable case-insensitive matching for scenarios where API keys might have inconsistent casing:

```
class User extends Model
{
    use HasApiRelations;

    public function profile()
    {
        return $this->hasOneApi(
            callback: fn($userIds) => Http::post('https://api.example.com/profiles', [
                'user_ids' => $userIds
            ])->json(),
            foreignKey: 'user_id',
            localKey: 'id',
            caseInsensitive: true  // Enable case-insensitive matching
        );
    }
}

// Example: Model has user_code = 'ABC'
// API returns data with user_code = 'abc' or 'Abc' or 'ABC'
// All variations will match successfully
```

**When to use case-insensitive matching:**

- External APIs return inconsistent key casing
- Legacy systems with mixed-case identifiers
- Case-insensitive database collations
- Multi-source data integration

### Composite Keys

[](#composite-keys)

Handle relationships with multiple key fields:

```
class Order extends Model
{
    use HasApiRelations;

    public function orderDetails()
    {
        return $this->hasOneApi(
            callback: fn($keys) => $this->fetchOrderDetails($keys),
            foreignKey: ['customer_id', 'order_number'],
            localKey: ['customer_id', 'order_number']
        );
    }

    private function fetchOrderDetails(array $compositeKeys): array
    {
        // $compositeKeys = [
        //     ['customer_id' => 1, 'order_number' => 'ORD-001'],
        //     ['customer_id' => 2, 'order_number' => 'ORD-002'],
        // ]

        $response = Http::post('https://api.example.com/order-details', [
            'keys' => $compositeKeys
        ]);

        return $response->json();
    }
}

// Usage
$order = Order::find(1);
$details = $order->orderDetails()->getResults();
```

### API Callback Format

[](#api-callback-format)

Your API callback receives an array of keys and should return an array of results:

**For hasOneApi:**

```
// Input: [1, 2, 3]
// Output: [
//     ['user_id' => 1, 'name' => 'John', 'email' => 'john@example.com'],
//     ['user_id' => 2, 'name' => 'Jane', 'email' => 'jane@example.com'],
//     ['user_id' => 3, 'name' => 'Bob', 'email' => 'bob@example.com'],
// ]
```

**For hasManyApi:**

```
// Input: [1, 2, 3]
// Output: [
//     ['user_id' => 1, 'title' => 'Post 1'],
//     ['user_id' => 1, 'title' => 'Post 2'],
//     ['user_id' => 2, 'title' => 'Post 3'],
//     ['user_id' => 3, 'title' => 'Post 4'],
//     ['user_id' => 3, 'title' => 'Post 5'],
// ]
```

### N+1 Prevention Example

[](#n1-prevention-example)

```
// ❌ BAD: N+1 problem (100 users = 100 API calls)
$users = User::all();
foreach ($users as $user) {
    $profile = $user->profile()->getResults(); // API call per user
}

// ✅ GOOD: Batch loading (100 users = 1 API call)
$users = User::with('profile')->get();
foreach ($users as $user) {
    $profile = $user->profile; // No additional API calls
}
```

How It Works
------------

[](#how-it-works)

1. **Lazy Loading**: When you access a relationship on a single model, it calls the API with that model's key
2. **Eager Loading**: When you use `with()`, it collects all keys from all models and makes a single batched API call
3. **Composite Keys**: Multiple fields are combined into an associative array and properly matched
4. **Result Matching**: API results are automatically matched back to the correct models using the foreign key

API Reference
-------------

[](#api-reference)

### hasOneApi

[](#hasoneapi)

Define a has-one API relationship.

```
public function hasOneApi(
    callable $apiCallback,
    string|array $foreignKey,
    string|array $localKey = 'id',
    bool $caseInsensitive = false
): HasOneApi
```

**Parameters:**

- `$apiCallback` - Function that receives array of keys and returns API results
- `$foreignKey` - Field name(s) in the API response to match against
- `$localKey` - Field name(s) in the local model (defaults to 'id')
- `$caseInsensitive` - Enable case-insensitive key matching (defaults to false)

**Returns:** `null` or array when no match found

### hasManyApi

[](#hasmanyapi)

Define a has-many API relationship.

```
public function hasManyApi(
    callable $apiCallback,
    string|array $foreignKey,
    string|array $localKey = 'id',
    bool $caseInsensitive = false
): HasManyApi
```

**Parameters:**

- `$apiCallback` - Function that receives array of keys and returns API results
- `$foreignKey` - Field name(s) in the API response to match against
- `$localKey` - Field name(s) in the local model (defaults to 'id')
- `$caseInsensitive` - Enable case-insensitive key matching (defaults to false)

**Returns:** Empty array `[]` when no matches found

Use Cases
---------

[](#use-cases)

Perfect for scenarios where you need to:

- Fetch user profiles from a separate authentication service
- Load product details from an external catalog API
- Retrieve order information from a third-party system
- Access microservice data while maintaining Eloquent-like syntax
- Handle multi-tenant relationships with composite keys

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

License
-------

[](#license)

This package is open-sourced software licensed under the [MIT license](LICENSE).

Credits
-------

[](#credits)

- [Carlin-rj](https://github.com/carlin-rj)

Support
-------

[](#support)

If you discover any issues, please email  or create an issue on GitHub.

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance73

Regular maintenance activity

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

154d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/545110c66db1405c084c9e1de3e2f4e12b4857b4ebf06b8074c3510a150fb826?d=identicon)[mckue](/maintainers/mckue)

---

Top Contributors

[![carlin-rj](https://avatars.githubusercontent.com/u/140769537?v=4)](https://github.com/carlin-rj "carlin-rj (1 commits)")

---

Tags

api-relationslaravel-packagerelationsapilaraveleloquentrelationseager-loadingComposite Key

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/carlin-laravel-api-relations/health.svg)

```
[![Health](https://phpackages.com/badges/carlin-laravel-api-relations/health.svg)](https://phpackages.com/packages/carlin-laravel-api-relations)
```

###  Alternatives

[cviebrock/eloquent-sluggable

Easy creation of slugs for your Eloquent models in Laravel

4.0k13.6M253](/packages/cviebrock-eloquent-sluggable)[cybercog/laravel-ban

Laravel Ban simplify blocking and banning Eloquent models.

1.1k651.8k11](/packages/cybercog-laravel-ban)[cybercog/laravel-love

Make Laravel Eloquent models reactable with any type of emotions in a minutes!

1.2k302.7k1](/packages/cybercog-laravel-love)[reedware/laravel-relation-joins

Adds the ability to join on a relationship by name.

2121.2M13](/packages/reedware-laravel-relation-joins)[typicms/nestablecollection

A Laravel Package that extends Collection to handle unlimited nested items following adjacency list model.

88327.2k20](/packages/typicms-nestablecollection)[sebastiaanluca/laravel-boolean-dates

Automatically convert Eloquent model boolean attributes to dates (and back).

40111.7k1](/packages/sebastiaanluca-laravel-boolean-dates)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
