Laravel Integration
Introduction
This guide shows you how to integrate Feedframer into your Laravel application using Laravel's HTTP client, caching, and best practices.
Installation & Setup
Step 1: Add API Key to Environment
Add your Feedframer API key to .env:
FEEDFRAMER_API_KEY=your_api_key_here
Step 2: Create Configuration File
Create config/services.php entry (or add to existing file):
// config/services.php
return [
// ... other services
'feedframer' => [
'api_key' => env('FEEDFRAMER_API_KEY'),
'base_url' => env('FEEDFRAMER_BASE_URL', 'https://feedframer.com'),
],
];
Basic Usage
Simple HTTP Request
use Illuminate\Support\Facades\Http;
$response = Http::get('https://feedframer.com/api/v1/me', [
'api_key' => config('services.feedframer.api_key'),
'page[size]' => 12,
]);
$posts = $response->json()['data'];
return view('instagram.feed', compact('posts'));
Controller Example
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class InstagramFeedController extends Controller
{
public function index()
{
$response = Http::get('https://feedframer.com/api/v1/me', [
'api_key' => config('services.feedframer.api_key'),
'filter[type]' => 'IMAGE',
'page[size]' => 12,
'sort' => '-published_at',
]);
if ($response->failed()) {
return view('instagram.feed', [
'posts' => [],
'error' => 'Unable to load Instagram posts'
]);
}
$data = $response->json();
return view('instagram.feed', [
'posts' => $data['data'],
'pagination' => $data['meta'],
]);
}
}
Blade Template
<!-- resources/views/instagram/feed.blade.php -->
<div class="instagram-feed">
@if(isset($error))
<div class="alert alert-error">{{ $error }}</div>
@endif
<div class="grid grid-cols-3 gap-4">
@foreach($posts as $post)
<div class="instagram-post">
<img src="{{ $post['mediaUrl'] }}"
alt="{{ Str::limit($post['caption'], 100) }}">
<div class="post-caption">
{{ $post['caption'] }}
</div>
<div class="post-stats">
<span>❤️ {{ $post['likeCount'] }}</span>
<span>💬 {{ $post['commentsCount'] }}</span>
</div>
<a href="{{ $post['permalink'] }}"
target="_blank"
class="view-on-instagram">
View on Instagram
</a>
</div>
@endforeach
</div>
</div>
Service Class Pattern
Create a dedicated service class for cleaner code:
Create Service
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class FeedframerService
{
protected string $apiKey;
protected string $baseUrl;
public function __construct()
{
$this->apiKey = config('services.feedframer.api_key');
$this->baseUrl = config('services.feedframer.base_url');
}
public function getPosts(array $params = [])
{
try {
$response = Http::get("{$this->baseUrl}/api/v1/me", array_merge([
'api_key' => $this->apiKey,
], $params));
if ($response->successful()) {
return $response->json();
}
$this->logError($response);
return null;
} catch (\Exception $e) {
Log::error('Feedframer API error: ' . $e->getMessage());
return null;
}
}
public function getImagePosts(int $limit = 12)
{
return $this->getPosts([
'filter[type]' => 'IMAGE',
'page[size]' => $limit,
'sort' => '-published_at',
]);
}
public function getVideoPosts(int $limit = 12)
{
return $this->getPosts([
'filter[type]' => 'VIDEO',
'page[size]' => $limit,
'sort' => '-published_at',
]);
}
public function getReels(int $limit = 12)
{
return $this->getPosts([
'filter[type]' => 'REELS',
'page[size]' => $limit,
'sort' => '-published_at',
]);
}
protected function logError($response)
{
$error = $response->json()['errors'][0] ?? null;
if ($error) {
Log::error('Feedframer API Error', [
'code' => $error['code'],
'detail' => $error['detail'],
'status' => $error['status'],
]);
}
}
}
Use Service in Controller
<?php
namespace App\Http\Controllers;
use App\Services\FeedframerService;
class InstagramFeedController extends Controller
{
public function __construct(
protected FeedframerService $feedframer
) {}
public function index()
{
$data = $this->feedframer->getImagePosts(12);
return view('instagram.feed', [
'posts' => $data['data'] ?? [],
]);
}
public function videos()
{
$data = $this->feedframer->getVideoPosts(12);
return view('instagram.videos', [
'posts' => $data['data'] ?? [],
]);
}
}
Caching
Cache API responses to reduce API calls and improve performance:
Basic Caching
use Illuminate\Support\Facades\Cache;
$posts = Cache::remember('instagram_posts', 3600, function () {
return Http::get('https://feedframer.com/api/v1/me', [
'api_key' => config('services.feedframer.api_key'),
'page[size]' => 12,
])->json();
});
Service with Caching
public function getPosts(array $params = [], bool $cached = true)
{
if (!$cached) {
return $this->fetchPosts($params);
}
$cacheKey = 'feedframer_posts_' . md5(json_encode($params));
return Cache::remember($cacheKey, 3600, function () use ($params) {
return $this->fetchPosts($params);
});
}
protected function fetchPosts(array $params)
{
$response = Http::get("{$this->baseUrl}/api/v1/me", array_merge([
'api_key' => $this->apiKey,
], $params));
return $response->successful() ? $response->json() : null;
}
Cache Invalidation
// Clear specific cache
Cache::forget('feedframer_posts_' . md5(json_encode($params)));
// Clear all Feedframer caches
Cache::flush(); // Not recommended - clears all cache
// Or use cache tags (Redis/Memcached only)
Cache::tags(['feedframer'])->flush();
GraphQL Integration
GraphQL Query
use Illuminate\Support\Facades\Http;
$query = <<<'GRAPHQL'
query {
posts(first: 12, type: "IMAGE") {
data {
id
caption
mediaUrl
publishedAt
instagramAccount {
username
}
}
}
}
GRAPHQL;
$response = Http::post('https://feedframer.com/graphql', [
'query' => $query,
], [
'api_key' => config('services.feedframer.api_key'),
]);
$posts = $response->json()['data']['posts']['data'];
GraphQL Service Method
public function queryGraphQL(string $query, array $variables = [])
{
$response = Http::post("{$this->baseUrl}/graphql", [
'query' => $query,
'variables' => $variables,
], [
'api_key' => $this->apiKey,
]);
if ($response->successful()) {
$data = $response->json();
if (isset($data['errors'])) {
Log::error('GraphQL errors', $data['errors']);
return null;
}
return $data['data'];
}
return null;
}
// Usage
$query = 'query { posts(first: 12) { data { id caption } } }';
$data = $this->feedframer->queryGraphQL($query);
$posts = $data['posts']['data'];
Filtering
Filter by Type
// Images only
$images = $this->feedframer->getPosts([
'filter[type]' => 'IMAGE',
'page[size]' => 24,
]);
// Videos only
$videos = $this->feedframer->getPosts([
'filter[type]' => 'VIDEO',
'page[size]' => 12,
]);
Filter by Date Range
use Carbon\Carbon;
$posts = $this->feedframer->getPosts([
'filter[publishedAfter]' => Carbon::now()->subDays(30)->toIso8601String(),
'filter[publishedBefore]' => Carbon::now()->toIso8601String(),
'page[size]' => 50,
]);
Pagination
Cursor-Based Pagination
public function getPaginatedPosts(Request $request)
{
$params = [
'page[size]' => 12,
];
if ($request->has('cursor')) {
$params['page[cursor]'] = $request->input('cursor');
}
$data = $this->feedframer->getPosts($params);
return view('instagram.feed', [
'posts' => $data['data'],
'nextCursor' => $data['meta']['next_cursor'],
'prevCursor' => $data['meta']['prev_cursor'],
]);
}
Blade Pagination Links
@if($nextCursor)
<a href="?cursor={{ $nextCursor }}" class="btn">Load More</a>
@endif
Rate Limiting
Check Rate Limits
$response = Http::get('https://feedframer.com/api/v1/me', [
'api_key' => config('services.feedframer.api_key'),
]);
$limit = $response->header('X-RateLimit-Limit');
$remaining = $response->header('X-RateLimit-Remaining');
Log::info("Rate limit: {$remaining}/{$limit} remaining");
Handle Rate Limit Errors
public function getPosts(array $params = [])
{
$response = Http::get("{$this->baseUrl}/api/v1/me", array_merge([
'api_key' => $this->apiKey,
], $params));
if ($response->status() === 429) {
Log::warning('Rate limit exceeded');
// Return cached data if available
return Cache::get('last_successful_posts');
}
if ($response->successful()) {
$data = $response->json();
Cache::put('last_successful_posts', $data, 3600);
return $data;
}
return null;
}
Error Handling
Comprehensive Error Handling
public function getPosts(array $params = [])
{
try {
$response = Http::timeout(10)->get("{$this->baseUrl}/api/v1/me", array_merge([
'api_key' => $this->apiKey,
], $params));
if ($response->successful()) {
return $response->json();
}
// Handle specific error codes
$error = $response->json()['errors'][0] ?? null;
if ($error) {
match ($error['code']) {
'INVALID_API_KEY' => Log::error('Invalid Feedframer API key'),
'RATE_LIMIT_EXCEEDED' => Log::warning('Feedframer rate limit exceeded'),
default => Log::error('Feedframer API error: ' . $error['detail']),
};
}
return null;
} catch (\Illuminate\Http\Client\ConnectionException $e) {
Log::error('Connection to Feedframer failed: ' . $e->getMessage());
return null;
}
}
View Composer
Share Instagram posts across multiple views:
// app/Providers/ViewServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Services\FeedframerService;
class ViewServiceProvider extends ServiceProvider
{
public function boot(FeedframerService $feedframer)
{
View::composer('partials.instagram-sidebar', function ($view) use ($feedframer) {
$data = $feedframer->getImagePosts(6);
$view->with('instagramPosts', $data['data'] ?? []);
});
}
}
<!-- resources/views/partials/instagram-sidebar.blade.php -->
<div class="instagram-sidebar">
<h3>Latest from Instagram</h3>
<div class="grid grid-cols-2 gap-2">
@foreach($instagramPosts as $post)
<a href="{{ $post['permalink'] }}" target="_blank">
<img src="{{ $post['mediaUrl'] }}"
alt="Instagram post"
class="rounded">
</a>
@endforeach
</div>
</div>
Scheduled Updates
Fetch and cache posts on a schedule:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
$feedframer = app(FeedframerService::class);
$data = $feedframer->getPosts(['page[size]' => 50]);
if ($data) {
Cache::put('instagram_feed', $data, 3600);
}
})->hourly();
}
Testing
Mock HTTP Responses
// tests/Feature/InstagramFeedTest.php
use Illuminate\Support\Facades\Http;
test('displays instagram feed', function () {
Http::fake([
'feedframer.com/*' => Http::response([
'username' => 'test_user',
'name' => 'Test User',
'posts' => [
[
'id' => '18123456789012345',
'caption' => 'Test post',
'mediaUrl' => 'https://example.com/image.jpg',
'mediaType' => 'IMAGE',
'permalink' => 'https://instagram.com/p/ABC123',
'timestamp' => '2024-01-15T18:30:00+00:00'
],
],
'pagination' => [
'nextCursor' => null,
'prevCursor' => null,
'hasMore' => false,
'perPage' => 12,
],
], 200),
]);
$response = $this->get('/instagram');
$response->assertOk();
$response->assertSee('Test post');
});
Best Practices
- Use Service Classes - Encapsulate API logic in dedicated services
- Cache Aggressively - Cache for at least 1 hour to reduce API calls
- Handle Errors Gracefully - Always provide fallback content
- Log Issues - Log API errors for debugging
- Environment Configuration - Store API keys in
.env - Rate Limit Awareness - Monitor rate limits and implement caching
Complete Example
Full working example:
// app/Services/FeedframerService.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class FeedframerService
{
protected string $apiKey;
protected string $baseUrl;
protected int $cacheMinutes = 60;
public function __construct()
{
$this->apiKey = config('services.feedframer.api_key');
$this->baseUrl = config('services.feedframer.base_url');
}
public function getLatestPosts(int $limit = 12): array
{
return Cache::remember("feedframer_latest_{$limit}", $this->cacheMinutes, function () use ($limit) {
$data = $this->makeRequest([
'page[size]' => $limit,
'sort' => '-published_at',
]);
return $data['data'] ?? [];
});
}
protected function makeRequest(array $params = []): ?array
{
try {
$response = Http::timeout(10)
->get("{$this->baseUrl}/api/v1/me", array_merge([
'api_key' => $this->apiKey,
], $params));
if ($response->successful()) {
return $response->json();
}
$this->handleError($response);
return null;
} catch (\Exception $e) {
Log::error('Feedframer request failed', [
'message' => $e->getMessage(),
'params' => $params,
]);
return null;
}
}
protected function handleError($response): void
{
$error = $response->json()['errors'][0] ?? null;
if ($error) {
Log::error('Feedframer API Error', [
'code' => $error['code'],
'detail' => $error['detail'],
]);
}
}
}
// app/Http/Controllers/InstagramFeedController.php
<?php
namespace App\Http\Controllers;
use App\Services\FeedframerService;
class InstagramFeedController extends Controller
{
public function index(FeedframerService $feedframer)
{
$posts = $feedframer->getLatestPosts(12);
return view('instagram.feed', compact('posts'));
}
}
// routes/web.php
Route::get('/instagram', [InstagramFeedController::class, 'index'])->name('instagram');