PHP - - By Younes Rafie

How to Build a YouTube API App with Laravel

In this two-part article, we’re going to learn how to work with version 3 of the YouTube API and we’ll build a demo along the way. Let’s get started.

YouTube Logo

What We’re Building

We’re going to build a demo that lets the user browse popular videos on YouTube, search for a video, browse videos by categories, and select a video to watch.

I will be using Laravel 5 as my framework of choice, and Vagrant for my development environment. The final product will look something like this:

Final demo

Preparing the Project

After installing Laravel 5 on your machine, install google/apiclient:

composer require google/apiclient

To set up a new project on the Google Developers Console, check out this quick tip to get started.

Registering Environment Variables

After getting our credentials from the Google Developers Console, we need to register them inside our application.

// .env

APP_DEBUG=true

app_name='Your app name (optional)'
client_id='Your client id'
client_secret='Your client secret'
api_key='Your developer key'
// config/google.php

return [
    'app_name'      => env('app_name'),
    'client_id'     => env('client_id'),
    'client_secret' => env('client_secret'),
    'api_key'       => env('api_key')
];

Laravel automatically loads the environment variables from the .env file in the root of our application folder. Now that we have our configuration set up, we can start the login process.

Login & Authorization

Before logging in with the Google API, we need to talk about scopes, and why they are important to the authorization process.

Scopes

Scopes define the level of authorization accorded to the application, so be sure to only request what you need. YouTube’s API has four scopes:

  • https://www.googleapis.com/auth/youtube: Manage your YouTube account.
  • https://www.googleapis.com/auth/youtube.readonly: View your YouTube account.
  • https://www.googleapis.com/auth/youtube.upload: Upload YouTube videos and manage your YouTube videos.
  • https://www.googleapis.com/auth/youtubepartner-channel-audit: Retrieve the auditDetails part in a channel resource.

For our demo, I’m only going to use the first one, and we can extend it depending on our application’s needs. You can read more about authorization in the documentation.

// app/Services/GoogleLogin.php

namespace App\Services;

class GoogleLogin
{
  protected $client;

  public function __construct(\Google_Client $client)
  {
    $this->client = $client;

    $this->client->setClientId(\Config::get('google.client_id'));
    $this->client->setClientSecret(\Config::get('google.client_secret'));
    $this->client->setDeveloperKey(\Config::get('google.api_key'));
    $this->client->setRedirectUri(\Config::get('app.url') . "/loginCallback");
    $this->client->setScopes([
                                 'https://www.googleapis.com/auth/youtube',
                             ]);
    $this->client->setAccessType('offline');
  }

  public function isLoggedIn()
  {
    if (\Session::has('token')) {
      $this->client->setAccessToken(\Session::get('token'));
    }

    if ($this->client->isAccessTokenExpired()) {
      \Session::set('token', $this->client->getRefreshToken());
    }

    return !$this->client->isAccessTokenExpired();
  }

  public function login($code)
  {
    $this->client->authenticate($code);
    $token = $this->client->getAccessToken();
    \Session::put('token', $token);

    return $token;
  }
  
  public function getLoginUrl()
  {
    $authUrl = $this->client->createAuthUrl();

    return $authUrl;
  }
}

After constructing a Google_Client instance using our credentials, we set the desired scopes. The setAccessType method gives our application the ability to refresh the token when the user is not present.

Login Controller

// app/Http/routes.php

Route::get('/login', ['uses' => 'GoogleLoginController@index', 'as' => 'login']);
Route::get('/loginCallback', ['uses' => 'GoogleLoginController@store', 'as' => 'loginCallback']);
// app/Http/Controllers/GoogleLoginController.php

class GoogleLoginController extends Controller
{

  public function index(\App\Services\GoogleLogin $ga)
  {
    if ($ga->isLoggedIn()) {
      return \Redirect::to('/');
    }

    $loginUrl = $ga->getLoginUrl();

    return "<a href='{$loginUrl}'>login</a>";
  }

  public function store(\App\Services\GoogleLogin $ga)
  {
    // User rejected the request
    if(\Input::has('error')){
      dd(\Input::get('error'));
    }

    if (\Input::has('code')) {
      $code = \Input::get('code');
      $ga->login($code);

      return \Redirect::to('/');
    } else {
      throw new \InvalidArgumentException("Code attribute is missing.");
    }//else
  }
}

The GoogleLoginController@index method will test if the user is logged in and if so, will redirect them to the home page. Otherwise, we generate a new login URL.

After passing the authorization phase, Google will redirect the user to the callback URL defined in the Google Developers Console with the code parameter. This code is used in exchange for a token.

Listing Videos

The Google_Service_YouTube class is our door to the YouTube API. It provides access to all YouTube data. The class takes a Google_Client instance as a parameter. It makes sense to create a provider to bind our implementation so that we don’t have to repeat the same code everywhere. You can read more about service providers in the documentation.

php artisan make:provider YouTubeServiceProvider

This command will create a new class inside our app/Providers folder. We need to add our service provider to the list of providers inside config/app.php.

// config/app.php

'providers' => [
  'App\Providers\YouTubeServiceProvider',
]
// app/Providers/YouTubeServiceProvider.php

public function register()
{
  $app = $this->app;

  $this->app->bind('GoogleClient', function () {
    $googleClient = new \Google_Client();
    $googleClient->setAccessToken(\Session::get("token"));

    return $googleClient;
  });

  $this->app->bind('youtube', function () use ($app) {
    $googleClient = \App::make('GoogleClient');
    $youtube = new \Google_Service_YouTube($googleClient);

    return $youtube;
  });
}

The https://www.googleapis.com/youtube/v3/videos endpoint returns the list of videos, and it can be accessed using our YouTube class via $youtube->videos.

// app/Http/Controllers/YouTubeAPIController.php

public function videos()
{
  $youtube = \App::make('youtube');
  $videos = $youtube->videos->listVideos('snippet', ['chart' => 'mostPopular']);

  dump($video);
}

Videos Screenshot

The first parameter to the listVideos method is called part and it defines the information contained in the result. You may add statistics to get data about votes count, likes, etc. We’ll talk more about the partparameter later in this article.You can read more about the supported values in the documentation.

The second parameter must contain a filter for the videos. In this case, we are getting the most popular videos on YouTube. You may also request your liked or disliked videos, etc. You can read more about filters in the documentation.

// app/Http/Controllers/YouTubeAPIController.php

public function videos()
{
  $options = ['chart' => 'mostPopular', 'maxResults' => 16];
  if (\Input::has('page')) {
    $options['pageToken'] = \Input::get('page');
  }

  $youtube = \App::make('youtube');
  $videos = $youtube->videos->listVideos('id, snippet', $options);

  return view('videos', ['videos' => $videos]);
}

This method is self explanatory. The page parameter is for pagination as we’ll see next.

// resources/views/videos.blade.php

<ul class="list-unstyled video-list-thumbs row">
@foreach($videos as $video)
    <li class="col-lg-3 col-sm-4 col-xs-6">
        <a href="http://youtube.com/watch?v={{ $video->getId() }}" title="{{ $video['snippet']['title'] }}" target="_blank">
            <img src="{{ $video['snippet']['thumbnails']['medium']['url'] }}" alt="{{ $video['snippet']['title'] }}" />
            <h2 class="truncate">{{ $video['snippet']['title'] }}</h2>
        </a>
    </li>
@endforeach
</ul>

<ul class="pagination pagination-lg">
    <li @if($videos->getPrevPageToken() == null) class="disabled" @endif>
      <a href="/videos?page={{$videos->getPrevPageToken()}}" aria-label="Previous">
        <span aria-hidden="true">Previous &laquo;</span>
      </a>
    </li>
    <li @if($videos->getNextPageToken() == null) class="disabled" @endif>
      <a href="/videos?page={{$videos->getNextPageToken()}}" aria-label="Next">
        <span aria-hidden="true">Next &raquo;</span>
      </a>
    </li>
</ul>

Our view is very basic – we only output the video title, thumbnail and a link. Our API response also contains likes, dislikes, view count, etc, so you may use them to enrich your page using some special markup. The pagination links give the user the ability to navigate through videos. You may have noticed that it’s done through tokens and not the normal page parameter.

When you click on a video, you will be redirected to watch the video on YouTube using the ID. Why not get the video details and create a page containing the player along with more info about the video?

Single Video Page

Getting a single video is just a matter of specifying an option to the same endpoint from before. Since the result is a single item, we set the maxResults parameter to 1, and we remove the chart attribute because it’s not relevant in this request.

// app/Http/routes.php

Route::get('/video/{id}', ['uses' => 'YouTubeAPIController@video', 'as' => 'video']);
// app/Http/Controllers/YouTubeAPIController.php

public function video($id){
  $options = ['maxResults' => 1, 'id' => $id];

  $youtube = \App::make('youtube');
  $videos = $youtube->videos->listVideos('id, snippet, player, contentDetails, statistics, status', $options);

  if(count($videos->getItems()) == 0){
    return redirect('404');
  }

  return view('video', ['video' => $videos[0]]);
}

Video

Because it’s a single item page, I tried to pull as much information as I can using the part parameter. If the item doesn’t exist, we redirect the user to the 404 not found page. Otherwise, we render our view with the first item from the list.

// resources/views/video.blade.php

<div class="row">
    <h2>{{ $video["snippet"]["title"] }}</h2>
</div>

<div class="row">
    <iframe type='text/html' src='http://www.youtube.com/embed/{{ $video->getId() }}' width='100%' height='500' frameborder='0' allowfullscreen='true'></iframe>
</div>

<div class="row">
    (<span>{{ $video["statistics"]["likeCount"] }} <i class="glyphicon glyphicon-thumbs-up"></i></span>)
    (<span>{{ $video["statistics"]["dislikeCount"] }} <i class="glyphicon glyphicon-thumbs-down"></i></span>)
    (<span>{{ $video["statistics"]["favoriteCount"] }} <i class="glyphicon glyphicon-heart"></i></span>)
    (<span>{{ $video["statistics"]["commentCount"] }} <i class="glyphicon glyphicon-bullhorn"></i></span>)
</div>

<hr/>

<div class="row">
    <p>{{ $video["snippet"]["description"] }}</p>
</div>

Because we asked the YouTube API for the player part, we can directly access it using $video['player']['embedHtml'], but if you want to customize the player dimensions you can build it using the video ID as we did in our example.

One thing to note: every request has a cost of one call, and when you ask for the snippet, statistics, etc, you add up new costs. You can read more about quotas and costs in the documentation.

Video

Wrapping Up

In this first part, we introduced the YouTube API and built a small demo to query the list of most popular videos on YouTube with the pagination links, and a page to view a single video. In the next part we will be exploring video categories and the search functionality, so stay tuned. You can check the final result on Github, and if you have any questions or comments, you can post them below.

Sponsors