In this tutorial, i’ll show you how to integrate Instagram API in Laravel App. We won’t be using any Instagram PHP wrapper library. Instead, we’ll use socialiteproviders/instagram provider to retrieve an access token from Instagram OAuth service and use it to make calls to Instagram API with guzzlehttp/guzzle client.

Getting Started

After creating a new project, run php artisan preset bootstrap to remove default VueJs scaffolding. Install Laravel Socialite, Instagram Socialite provider, and GuzzleHttp package.

composer require laravel/socialite composer require socialiteproviders/instagram composer require guzzlehttp/guzzle 1 2 3 composer require laravel / socialite composer require socialiteproviders / instagram composer require guzzlehttp / guzzle

Instagram Socialite Provider Configuration

After installation, add \SocialiteProviders\Manager\ServiceProvider::class to service providers array in config/app.php file.

<?php return [ 'providers' => [ [...] // other service providers \SocialiteProviders\Manager\ServiceProvider::class, ], 'aliases' => [...], ]; 1 2 3 4 5 6 7 8 9 10 11 <?php return [ 'providers' = > [ [ . . . ] // other service providers \ SocialiteProviders \ Manager \ ServiceProvider:: class , ] , 'aliases' = > [ . . . ] , ] ;

Now edit app/Providers/EventServiceProvider and add SocialiteWasCalled event to your listen[] array in app/Providers/EventServiceProvider .

<?php namespace App\Providers; use [...] class EventServiceProvider extends ServiceProvider { protected $listen = [ 'App\Events\Event' => [ 'App\Listeners\EventListener', ], \SocialiteProviders\Manager\SocialiteWasCalled::class => [ // add your listeners (aka providers) here 'SocialiteProviders\\Instagram\\InstagramExtendSocialite@handle', ], ]; public function boot(){...} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php namespace App \ Providers ; use [ . . . ] class EventServiceProvider extends ServiceProvider { protected $listen = [ 'App\Events\Event' = > [ 'App\Listeners\EventListener' , ] , \ SocialiteProviders \ Manager \ SocialiteWasCalled:: class = > [ // add your listeners (aka providers) here 'SocialiteProviders\\Instagram\\InstagramExtendSocialite@handle' , ] , ] ; public function boot ( ) { . . . } }

Now add Instagram config to config/service.php array.

'instagram' => [ 'client_id' => env('INSTAGRAM_KEY'), 'client_secret' => env('INSTAGRAM_SECRET'), 'redirect' => env('INSTAGRAM_REDIRECT_URI') ], 1 2 3 4 5 'instagram' = > [ 'client_id' = > env ( 'INSTAGRAM_KEY' ) , 'client_secret' = > env ( 'INSTAGRAM_SECRET' ) , 'redirect' = > env ( 'INSTAGRAM_REDIRECT_URI' ) ] ,

Registering a new Instagram Client

Register a new Instagram client. Fill in the details. Make sure your URI must match env('INSTAGRAM_REDIRECT_URI'). User will be redirected back to this URI after authorization.

After registration, your app will show on the dashboard. Click manage and copy client id and secret. Edit .env file and add these credentials to it.

INSTAGRAM_KEY=YOUR_APP_ID INSTAGRAM_SECRET=YOUR_APP_SECRET INSTAGRAM_REDIRECT_URI=https://127.0.0.1/instagram/callback 1 2 3 INSTAGRAM_KEY = YOUR_APP_ID INSTAGRAM_SECRET = YOUR_APP_SECRET INSTAGRAM_REDIRECT_URI = https : //127.0.0.1/instagram/callback

Generate Authentication Scaffolding

Run php artisan make:auth to generate default authentication scaffolding. We’ll authenticate users with conventional form based authentication. After authentication, we’ll ask them to authorize with Instagram OAuth. We’re doing this because Instagram doesn’t provide an email address. We’ll create a separate table for storing user’s Instagram access tokens and retrieve them with HasOne relation.

Migrations

We are creating two tables, users and instagram. Registered users will be stored on users table and their access token on instagram table along with their user_id. CreateUsersTable migration comes pre-created with laravel app. Run

php artisan make:migration CreateInstagramTable --create=instagram

to create a new migration and add these columns to it.

<?php class CreateInstagramTable extends Migration { public function up() { Schema::create('instagram', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->string('access_token'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->timestamps(); }); } public function down(){...} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class CreateInstagramTable extends Migration { public function up ( ) { Schema:: create ( 'instagram' , function ( Blueprint $table ) { $table -> increments ( 'id' ) ; $table -> integer ( 'user_id' ) -> unsigned ( ) ; $table -> string ( 'access_token' ) ; $table -> foreign ( 'user_id' ) -> references ( 'id' ) -> on ( 'users' ) -> onDelete ( 'cascade' ) ; $table -> timestamps ( ) ; } ) ; } public function down ( ) { . . . } }

We’re creating user_id column and referencing it to id column on users table. We also specified an onDelete action when user record will be deleted. After adding your database credentials to .env file, run php artisan migrate to create database tables.

Models

Create an Instagram model with php artisan make:model Instagram

<?php namespace App; use Illuminate\Database\Eloquent\Model; class Instagram extends Model { protected $table = 'instagram'; protected $fillable = [ 'user_id', 'insta_id', 'access_token', ]; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php namespace App ; use Illuminate \ Database \ Eloquent \ Model ; class Instagram extends Model { protected $table = 'instagram' ; protected $fillable = [ 'user_id' , 'insta_id' , 'access_token' , ] ; }

Modify your existing User model and add a method to retrieve an associated record from instagram table with One to One relation.

<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', ]; public function instagram(){ return $this->hasOne(Instagram::class, 'user_id', 'id'); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php namespace App ; use Illuminate \ Notifications \ Notifiable ; use Illuminate \ Foundation \ Auth \ User as Authenticatable ; class User extends Authenticatable { use Notifiable ; protected $fillable = [ 'name' , 'email' , 'password' , ] ; protected $hidden = [ 'password' , 'remember_token' , ] ; public function instagram ( ) { return $this -> hasOne ( Instagram:: class , 'user_id' , 'id' ) ; } }

Now we’ll be able to verify if a user has an access token record on instagram table with Auth::user()->instagram.

Routes

<?php Auth::routes(); Route::group(['middleware' => ['auth']], function(){ Route::get('/', 'AppController@index'); Route::get('/search', 'AppController@search'); Route::get('/instagram', 'InstagramController@redirectToInstagramProvider'); Route::get('/instagram/callback', 'InstagramController@handleProviderInstagramCallback'); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php Auth:: routes ( ) ; Route:: group ( [ 'middleware' = > [ 'auth' ] ] , function ( ) { Route:: get ( '/' , 'AppController@index' ) ; Route:: get ( '/search' , 'AppController@search' ) ; Route:: get ( '/instagram' , 'InstagramController@redirectToInstagramProvider' ) ; Route:: get ( '/instagram/callback' , 'InstagramController@handleProviderInstagramCallback' ) ; } ) ;

We’ve moved all routes to an authenticated route group. After authentication, user will be redirected to / URI. /instagram route will redirect users to Instagram OAuth service and /instagram/callback will receive user Object after authorization.

Instagram API Class

Create Classes folder under app directory. Now create an InstagramAPI class in it. We’ll write our API logic in this class and then bind it to the service container. We’ll also create a Facade and Alias to call its methods statically.

<?php namespace App\Classes; use GuzzleHttp\Client; class InstagramAPI{ private $client; private $access_token; public function __construct() { $this->client = new Client([ 'base_uri' => 'https://api.instagram.com/v1/', ]); } public function setAccessToken($token){ $this->access_token = $token; } public function getUser(){ if($this->access_token){ $response = $this->client->request('GET', 'users/self/', [ 'query' => [ 'access_token' => $this->access_token ] ]); return json_decode($response->getBody()->getContents())->data; } return []; } public function getPosts(){ if($this->access_token){ $response = $this->client->request('GET', 'users/self/media/recent/', [ 'query' => [ 'access_token' => $this->access_token ] ]); return json_decode($response->getBody()->getContents())->data; } return []; } public function getTagPosts($tags){ if($this->access_token){ $response = $this->client->request('GET', 'tags/'.$tags.'/media/recent/', [ 'query' => [ 'access_token' => $this->access_token ] ]); return json_decode($response->getBody()->getContents())->data; } return []; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <?php namespace App \ Classes ; use GuzzleHttp \ Client ; class InstagramAPI { private $client ; private $access_token ; public function __construct ( ) { $this -> client = new Client ( [ 'base_uri' = > 'https://api.instagram.com/v1/' , ] ) ; } public function setAccessToken ( $token ) { $this -> access_token = $token ; } public function getUser ( ) { if ( $this -> access_token ) { $response = $this -> client -> request ( 'GET' , 'users/self/' , [ 'query' = > [ 'access_token' = > $this -> access_token ] ] ) ; return json_decode ( $response -> getBody ( ) -> getContents ( ) ) -> data ; } return [ ] ; } public function getPosts ( ) { if ( $this -> access_token ) { $response = $this -> client -> request ( 'GET' , 'users/self/media/recent/' , [ 'query' = > [ 'access_token' = > $this -> access_token ] ] ) ; return json_decode ( $response -> getBody ( ) -> getContents ( ) ) -> data ; } return [ ] ; } public function getTagPosts ( $tags ) { if ( $this -> access_token ) { $response = $this -> client -> request ( 'GET' , 'tags/' . $tags . '/media/recent/' , [ 'query' = > [ 'access_token' = > $this -> access_token ] ] ) ; return json_decode ( $response -> getBody ( ) -> getContents ( ) ) -> data ; } return [ ] ; } }

In the constructor, we’re creating an instance of GuzzleHttp Client by passing a base URI to it. Then we have a setAccessToken() method that we’ll statically call from a middleware to set user’s access token. We’ll call getUser() , getPosts() and getTagPosts() method to retrieve data from API in controllers.

Service Provider

We’re creating a service provider to bind InstagramAPI class to the service container. Run php artisan make:provider InstagramServiceProvider to create a new service provider and add this code to it.

<?php namespace App\Providers; use App\Classes\InstagramAPI; use Illuminate\Support\ServiceProvider; class InstagramServiceProvider extends ServiceProvider { public function boot() { // } public function register() { $this->app->bind('InstagramAPI', function(){ return new InstagramAPI(); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php namespace App \ Providers ; use App \ Classes \ InstagramAPI ; use Illuminate \ Support \ ServiceProvider ; class InstagramServiceProvider extends ServiceProvider { public function boot ( ) { // } public function register ( ) { $this -> app -> bind ( 'InstagramAPI' , function ( ) { return new InstagramAPI ( ) ; } ) ; } }

Now add your service provider to config/app.php providers[] array.

'providers' => [ [...] // other providers \SocialiteProviders\Manager\ServiceProvider::class, \App\Providers\InstagramServiceProvider::class, ], 1 2 3 4 5 'providers' = > [ [ . . . ] // other providers \ SocialiteProviders \ Manager \ ServiceProvider:: class , \ App \ Providers \ InstagramServiceProvider:: class , ] ,

Facade

To use InstagramAPI class methods statically, we’ll create a facade for it. Create Facade folder in your app directory and create an InstagramFacade.php file under it. Add this code to it.

<?php namespace App\Facades; use Illuminate\Support\Facades\Facade; class Instagram extends Facade { protected static function getFacadeAccessor() { return 'InstagramAPI'; } } 1 2 3 4 5 6 7 8 9 <?php namespace App \ Facades ; use Illuminate \ Support \ Facades \ Facade ; class Instagram extends Facade { protected static function getFacadeAccessor ( ) { return 'InstagramAPI' ; } }

I’ll be using this Facade in controllers. If you want to, you can create an Alias for it. Add this line to config/app.php aliases[] array.

'Instagram' => App\Facades\Instagram::class

Middleware

We need to call setAccessToken() method to set user access token before using any of InstagramAPI methods in our controllers. Since sessions data is not available in service providers, the only way to do it is to call it in a controller’s constructor or middleware. Run php artisan make:middleware InstagramAPIMiddleware to create a new middleware and add this code to it.

<?php namespace App\Http\Middleware; use App\Facades\Instagram; use Closure; use Illuminate\Support\Facades\Auth; class InstagramAPIMiddleware { public function handle($request, Closure $next) { if(Auth::user()->instagram){ Instagram::setAccessToken(Auth::user()->instagram->access_token); } return $next($request); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php namespace App \ Http \ Middleware ; use App \ Facades \ Instagram ; use Closure ; use Illuminate \ Support \ Facades \ Auth ; class InstagramAPIMiddleware { public function handle ( $request , Closure $next ) { if ( Auth:: user ( ) -> instagram ) { Instagram:: setAccessToken ( Auth:: user ( ) -> instagram -> access_token ) ; } return $next ( $request ) ; } }

Also, add this middleware to app\Http\Kernal.php routerMiddleware[] array.

protected $routeMiddleware = [ [...] 'instagram' => \App\Http\Middleware\InstagramAPIMiddleware::class, ]; 1 2 3 4 protected $routeMiddleware = [ [ . . . ] 'instagram' = > \ App \ Http \ Middleware \ InstagramAPIMiddleware:: class , ] ;

Now apply this middleware to authenticated route group in app/routes/web.php file.

Route::group(['middleware' => ['auth', 'instagram']], function(){ // routes }); 1 2 3 Route:: group ( [ 'middleware' = > [ 'auth' , 'instagram' ] ] , function ( ) { // routes } ) ;

Controllers

In our InstagramController, we’ve two methods.

<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Auth; use Laravel\Socialite\Facades\Socialite; class InstagramController extends Controller { public function redirectToInstagramProvider() { return Socialite::with('instagram')->scopes([ "public_content"])->redirect(); } public function handleProviderInstagramCallback() { $insta = Socialite::driver('instagram')->user(); $details = [ "access_token" => $insta->token ]; if(Auth::user()->instagram){ Auth::user()->instagram()->update($details); }else{ Auth::user()->instagram()->create($details); } return redirect('/'); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php namespace App \ Http \ Controllers ; use Illuminate \ Support \ Facades \ Auth ; use Laravel \ Socialite \ Facades \ Socialite ; class InstagramController extends Controller { public function redirectToInstagramProvider ( ) { return Socialite:: with ( 'instagram' ) -> scopes ( [ "public_content" ] ) -> redirect ( ) ; } public function handleProviderInstagramCallback ( ) { $insta = Socialite:: driver ( 'instagram' ) -> user ( ) ; $details = [ "access_token" = > $insta -> token ] ; if ( Auth:: user ( ) -> instagram ) { Auth:: user ( ) -> instagram ( ) -> update ( $details ) ; } else { Auth:: user ( ) -> instagram ( ) -> create ( $details ) ; } return redirect ( '/' ) ; } }

redirectToInstagramProvider() will redirect user to OAuth server and handleProviderInstagramCallback() method receives a user object after user authorizes. We’re creating a new related instagram model record if it doesn’t exist. The new record will have an access token and user_id of the authenticated user. If a record already exists, we simply update the access token.

<?php namespace App\Http\Controllers; use App\Facades\Instagram; use Illuminate\Http\Request; class AppController extends Controller { public function index() { return view('index') ->with('user', Instagram::getUser()) ->with('posts', Instagram::getPosts()); } public function search(Request $request){ return view('search') ->with('posts', Instagram::getTagPosts($request->tag)); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php namespace App \ Http \ Controllers ; use App \ Facades \ Instagram ; use Illuminate \ Http \ Request ; class AppController extends Controller { public function index ( ) { return view ( 'index' ) -> with ( 'user' , Instagram:: getUser ( ) ) -> with ( 'posts' , Instagram:: getPosts ( ) ) ; } public function search ( Request $request ) { return view ( 'search' ) -> with ( 'posts' , Instagram:: getTagPosts ( $request -> tag ) ) ; } }

In our AppController, we’re using Instagram Facade to retreieve user info, posts and tagged posts and then passing to our index.blade.php php view.

Views

@extends('layouts.app') @section('content') <div class="container app"> @if(Auth::user()->instagram) <div class="profile-header"> <div class="col-xs-12 col-sm-12 col-md-2 col-lg-2 picture"> <img src="{{$user->profile_picture}}" alt="..."> </div> <div class="col-xs-12 col-sm-12 col-md-10 col-lg-10 info"> <h1>{{$user->username}}</h1> <span> Posts({{$user->counts->media}}) . Followers({{$user->counts->followed_by}}) . Following({{$user->counts->follows}}) </span> </div> </div> <div class="posts col-xs-12 col-sm-12 col-md-8 col-lg-6"> @if($posts) @foreach($posts as $post) <div class="post"> <div class="caption"> <h4>{{$post->caption->text}}</h4> </div> @if($post->type === 'image') <div class="image"> <img src="{{$post->images->standard_resolution->url}}" alt=""> </div> @else @endif </div> @endforeach @else No Instagram posts @endif </div> @else <a href="/instagram" class="btn btn-default"> Authenticate with Instagram </a> @endif </div> @endsection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @ extends ( 'layouts.app' ) @ section ( 'content' ) <div class = "container app" > @ if ( Auth :: user ( ) -> instagram) <div class = "profile-header" > <div class = "col-xs-12 col-sm-12 col-md-2 col-lg-2 picture" > <img src = "{{$user->profile_picture}}" alt = "..." > </div> <div class = "col-xs-12 col-sm-12 col-md-10 col-lg-10 info" > <h1> { { $ user -> username}} </h1> <span> Posts ( { { $ user -> counts -> media } } ) . Followers ( { { $ user -> counts -> followed _ by } } ) . Following ( { { $ user -> counts -> follows}}) </span> </div> </div> <div class = "posts col-xs-12 col-sm-12 col-md-8 col-lg-6" > @if($posts) @foreach($posts as $post) <div class = "post" > <div class = "caption" > <h4> { { $ post -> caption -> text}} </h4> </div> @ if ( $ post -> type === 'image') <div class = "image" > <img src = "{{$post->images->standard_resolution->url}}" alt = "" > </div> @else @endif </div> @endforeach @else No Instagram posts @endif </div> @else <a href = "/instagram" class = "btn btn-default" > Authenticate with Instagram </a> @endif </div> @ endsection

In our index.blade.php view, We’ll render a button instead of content if the authenticated user doesn’t have a record on instagram table. If a user has already authorized Instagram access, we’ll render their profile info along with posts.

Exception Handling

To handle exceptions thrown by GuzzleHttp, update app\Exceptions\Handlers.php render() method.

public function render($request, Exception $exception) { if($exception instanceof GuzzleException){ return response('An error occurred when making request to InstagramAPI'); } return parent::render($request, $exception); } 1 2 3 4 5 6 7 public function render ( $request , Exception $exception ) { if ( $exception instanceof GuzzleException ) { return response ( 'An error occurred when making request to InstagramAPI' ) ; } return parent :: render ( $request , $exception ) ; }

You can render custom response for errors raised by GuzzleHttp. You can raise your own custom errors and handle them here.

Instagram made some serious changes to their API after Facebook’s privacy issues. Many API endpoints for posting media and comments are deprecated. Read more about it here. I’ve set up an example repository for you. If you’ve any issues or errors, please comment. I’ll try to help you with it.