# APIs The Eloquent Way

> **Heads up: this was a technical demo.**  
> The code in this article was built as a showcase.

## Motivation

Almost every modern project requires third-party API integration — weather forecasts, stock data, and countless other use cases. While some APIs offer SDKs, many don't.

Quick access using ZTTP looks like:

```php
$symbols = \Zttp\Zttp::get("https://api.iextrading.com/1.0/ref-data/symbols")->json();
```

As complexity grows, you need abstraction for errors, validation, and transformation. The goal: access APIs the way you access a database:

```php
$user = User::where('email', $email)->first();
```

What if APIs worked the same way?

```php
$symbol = IEX::Symbol()->where('symbol', $apiSymbol)->first();
```

## Solution

The approach introduces an **API → Endpoint → Shape** pattern:

- **ApiConsumer** — represents one API (e.g., JSONPlaceholder)
- **Endpoint** — represents one resource (e.g., `/posts`), with chainable query methods
- **Shape** — represents the data structure returned

Endpoints mirror Eloquent's query builder. Shapes mirror models. All results return as collections, enabling methods like `filter()` when the API itself doesn't support filtering:

```php
$users = User::all();
$users = $users->filter(function ($value, $key) {
    return $value->age >= 21;
});
```

Shapes also support mutator-style custom methods (e.g., combining `first_name` + `last_name`), structural validation, and field normalization via `$transformations`.

Relations between endpoints are supported via `hasOne()` and `hasMany()`:

```php
$user = JSONPlaceholder::Post()->first()->user();
```

## Results

The package [`black-bits/laravel-api-consumer`](https://packagist.org/packages/black-bits/laravel-api-consumer) provides:

- Artisan generators for ApiConsumers, Endpoints, and Shapes
- Chainable collection methods on Endpoints
- Relation support between resources
- Built-in caching out of the box

## Show Case

Two APIs were implemented: [IEX Trading](https://iextrading.com/developer/docs/) and [JSONPlaceholder](https://jsonplaceholder.typicode.com/).

### ApiConsumers Setup

```bash
composer require black-bits/laravel-api-consumer
php artisan vendor:publish --provider="BlackBits\ApiConsumer\ApiConsumerServiceProvider"
php artisan make:api-consumer IEX
php artisan make:api-consumer-endpoint Symbol -c IEX
```

Add base URL to `/config/api-consumers.php`:

```php
return [\
    'IEX' => [\
        'apiBasePath' => 'https://api.iextrading.com/1.0',\
    ],\
];
```

ApiConsumer class (`/app/ApiConsumers/IEX/IEX.php`):

```php
namespace App\ApiConsumers\IEX;

use BlackBits\ApiConsumer\ApiConsumer;

class IEX extends ApiConsumer
{
    protected function getEndpoint()
    {
        return config('api-consumers.IEX.apiBasePath');
    }
}
```

Endpoint class with caching (`SymbolEndpoint.php`):

```php
class SymbolEndpoint extends Endpoint
{
    protected $path = '/ref-data/symbols';
    protected $shouldCache = true;
    protected $cacheDurationInMinutes = 10;

public function all()
    {
        return $this->get();
    }
}
```

Shape class with transformations and relations (`SymbolShape.php`):

```php
class SymbolShape extends BaseShape
{
    protected $return_shape_data_only = true;
    protected $require_shape_structure = true;

protected $transformations = [\
        'symbol'    => 'symbol',\
        'name'      => 'name',\
        'date'      => 'date',\
        'isEnabled' => 'is_enabled',\
        'type'      => 'type',\
        'iexId'     => 'iex_id'\
    ];

protected $fields = ['symbol', 'name', 'date', 'is_enabled', 'type', 'iex_id'];

public function company()
    {
        return $this->hasOne(CompanyEndpoint::class, 'symbol');
    }
}
```

### Usage

```php
$symbols = IEX::Symbol()->all();

// With relation
$company = IEX::Symbol()->first()->company();
```

Controller example with filtering:

```php
public function list(Request $request)
{
    $symbols = IEX::Symbol();

if (!empty($request->get('symbol'))) {
        $symbol = $request->get('symbol');
        $symbols->filter(function ($value, $key) use ($symbol) {
            return str_contains($value->symbol, $symbol);
        });
    }

if (!empty($request->get('name'))) {
        $symbols->filterName($request->get('name'));
    }

$data = ['symbols' => $symbols->take(10)->get()];
    return response()->view('symbols.list', $data);
}
```

JSONPlaceholder example — fetch a post with its comments:

```php
public function listComments($post)
{
    $post = JSONPlaceholder::Post()->where('id', $post)->first();

$data = [\
        'comments' => $post->comments(),\
        'post'     => $post\
    ];

return response()->view('json-placeholder.comments-list', $data);
}
```

## Next Steps

- Authentication support (next priority)
- Additional HTTP methods (POST, PUT, etc.)
- Improved relation functionality
- Testing
- Long-term: auto-generation for standardized endpoints (JSON API / Swagger)

## Conclusion

The package aims to fill a gap in the Laravel ecosystem by offering a standardized, Eloquent-style interface for third-party API consumption. Open questions for feedback:

- Thoughts on the Consumer → Endpoint → Shape structure?
- Missing features?
- Which APIs cause the most implementation friction?
