neomerx/limoncello
Integration layer between neomerx/json-api and Symfony-based apps, used by Limoncello quick-start projects (Laravel Limoncello Collins and Lumen Limoncello Shot). Provides JSON:API wiring and conventions; see the wiki for setup and usage.
Installation:
composer require neomerx/limoncello
Add to config/app.php under providers:
Neomerx\Limoncello\LimoncelloServiceProvider::class,
Basic Configuration: Publish the config file:
php artisan vendor:publish --provider="Neomerx\Limoncello\LimoncelloServiceProvider"
Configure API endpoints in config/limoncello.php:
'endpoints' => [
'default' => [
'base_uri' => 'https://api.example.com/v1',
'auth' => 'basic',
'username' => env('API_USERNAME'),
'password' => env('API_PASSWORD'),
],
],
First API Call:
use Neomerx\Limoncello\Client;
$client = app(Client::class);
$response = $client->get('users');
$data = $response->getData();
config/limoncello.php (API endpoints, defaults)app/Providers/LimoncelloServiceProvider.php (custom bindings if needed)vendor/neomerx/limoncello/src/Client.php (core client logic)// Basic Auth (configured in config)
$client->get('protected/resource');
// Custom Auth (per-request)
$client->withAuth('oauth', [
'token' => 'your_oauth_token'
])->get('oauth/resource');
// GET with query params
$response = $client->get('users', [
'filter' => ['active' => true],
'limit' => 10
]);
// POST with data
$response = $client->post('users', [
'name' => 'John Doe',
'email' => 'john@example.com'
]);
// Parse response
$data = $response->getData();
$status = $response->getStatusCode();
try {
$response = $client->get('users/123');
$data = $response->getData();
} catch (\Neomerx\Limoncello\Exception\RequestException $e) {
Log::error('API Error: ' . $e->getMessage());
// Handle 4xx/5xx responses
}
Attach middleware to modify requests/responses:
$client->pushMiddleware(function ($request) {
$request->setHeader('X-Custom-Header', 'value');
});
$client->pushResponseMiddleware(function ($response) {
if ($response->getStatusCode() === 404) {
// Custom 404 logic
}
});
Use middleware to enforce rate limits:
$client->pushMiddleware(function ($request) {
static $count = 0;
if ($count++ >= 10) {
throw new \RuntimeException('Rate limit exceeded');
}
});
$this->app->bind('api.v2', function () {
return app(Client::class)->setEndpoint('v2');
});
// app/Facades/Limoncello.php
public static function get($endpoint, $params = []) {
return app(Client::class)->get($endpoint, $params);
}
dispatch(new FetchUsersFromApiJob($client));
Mockery or Laravel's Http mock:
$mock = Mockery::mock(Client::class);
$mock->shouldReceive('get')->andReturn($response);
$this->app->instance(Client::class, $mock);
Deprecated Package:
No Built-in Retry Logic:
$attempts = 0;
while ($attempts < 3) {
try {
$response = $client->get('resource');
break;
} catch (\Exception $e) {
$attempts++;
sleep(2 ** $attempts);
}
}
Limited Middleware Support:
No Native JSON:API Support:
$client->pushResponseMiddleware(function ($response) {
$data = $response->getData();
return collect($data['data'])->map(function ($item) {
return [
'id' => $item['id'],
'type' => $item['type'],
'attrs' => $item['attributes'],
];
});
});
Config Overrides:
// Risky: Auth may reset if chained
$client->withAuth('bearer', ['token' => 'abc'])->get('resource')->post('data');
Enable Guzzle Debugging:
Add to config/limoncello.php:
'debug' => env('APP_DEBUG', false),
Logs requests/responses to storage/logs/limoncello.log.
Inspect Raw Responses:
$response = $client->get('resource');
\Log::debug('Raw Response:', [
'status' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'body' => $response->getBody(),
]);
Validate API Specs: Use tools like Postman or Insomnia to verify endpoints before integrating.
Custom Response Classes:
Extend \Neomerx\Limoncello\Response to add domain-specific methods:
class ApiResponse extends \Neomerx\Limoncello\Response {
public function getUser() {
return $this->getData()['user'];
}
}
Bind in LimoncelloServiceProvider:
$this->app->bind(
\Neomerx\Limoncello\Response::class,
ApiResponse::class
);
Plugin System: Create a trait for reusable API logic:
trait ApiClient {
public function fetchUser($id) {
return app(Client::class)->get("users/{$id}");
}
}
Event Dispatching: Trigger events on successful/failed requests:
$client->pushMiddleware(function ($request) {
event(new ApiRequestStarted($request));
});
$client->pushResponseMiddleware(function ($response) {
if ($response->isSuccessful()) {
event(new ApiRequestSucceeded($response));
} else {
event(new ApiRequestFailed($response));
}
});
Connection Pooling:
Reuse the same Client instance for all requests to leverage HTTP connection pooling:
$client = app(Client::class); // Singleton
Caching Responses: Cache frequent, immutable responses:
$cacheKey = "api_users_{$limit}";
$data = Cache::remember($cacheKey, now()->addHours(1), function () use ($client, $limit) {
return $client->get('users', ['limit' => $limit])->getData();
});
Async Requests: Use Laravel Queues for non-critical API calls:
dispatch(function () use ($client) {
$client->get('analytics')->then(function ($response) {
// Process async
});
});
How can I help you explore Laravel packages today?