Build a News web app with Laravel

In this tutorial, I will show you how to create a web application that gathers top news across multiple news sites, using News API and Laravel.

News API is a simple and easy-to-use API that returns JSON metadata for the headlines currently published on a range of news sources and blogs.

At the end of this tutorial, you will be able to build this app.

First, visit Laravel to get started with setting up Laravel.

Our Checklist:

  1. Start a new laravel app.
  2. Install Guzzle via composer.
  3. Create an account on News API.
  4. Generate an API key. This will be required to access the endpoints.
  5. Create an App Model. Here we will connect to the endpoints provided by News ApI.
  6. Connect to News API via guzzle. (Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services. read about it here ).
  7. Create an ApiController.php file.
  8. Edit the route file.
  9. Edit welcome.blade.php file to handle our view.
  10. Create a CSS file.
  11. Create a Javascript file to handle our POST request.

Steps 1 and 2:

Run this command on the terminal

laravel new news-app

Navigate into the new app directory by running

cd news-app

Run this command on the terminal also

composer require guzzlehttp/guzzle

The first ccommand laravel new news-app creates a new laravel app in your root folder while the second command adds Guzzle into your repo.


Tips: Run this command php artisan serve on your console to view your site, If you are on a mac, use Valet to serve your app( less hassle, less stress).


Step 3 and 4:

Log on to News API and copy out your api key (P.S, You will need to create one). We will also need to copy out the two endpoints needed for this app. (Not to worry, I copied it out for you below). Read more about these endpoints here.

'https://newsapi.org/v1/sources?language=en'    
and
'https://newsapi.org/v1/articles?source=cnn&sortBy=top&apiKey=YOUR_API_KEY'

The first link gets all news sources available, while the second link gets top news by cnn.

Question: Ehmm, but we do not want to be limited to cnn news alone, how do I do that?

Answer: Do not worry my dear friend, we will tweak this to our good from our codebase.


Step 5 and 6:

Next, we create an Api model.

Run this command on the terminal, Remember, you must be in your project directory to run this news-app

php artisan make:model Api

Open the newly created Api file located at news-app/app/Api.php. We will do two things: 1. Connect to the endpoints provided by News Api using guzzle. 2. Peradventure the API returns an invalid response, we should be able to make provisions to catch these Exceptions.


<?php
namespace App;

use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7;

class Api{
     public function getNews($source)
    {
        try {

              $client               = new GuzzleHttpClient();
              //with the code below, we can get news from multiple sources 
              $apiRequest       = $client->request('GET', 'https://newsapi.org/v1/articles?source='.$source.'&sortBy=top&apiKey=ADD_YOUR_API_KEY_HERE' );
              $content        = json_decode($apiRequest->getBody()->getContents(), true);

            return $content['articles'];
            } catch (RequestException $e) {
              //For handling exception
                    echo Psr7\str($e->getRequest());
                    if ($e->hasResponse()) {
                        echo Psr7\str($e->getResponse());
                    }
            }
    }

    public function getAllSources()
    {
        try {
              $client           = new GuzzleHttpClient(); 
                $apiRequest       = $client->request('GET', 'https://newsapi.org/v1/sources?language=en' );
              $content          = json_decode($apiRequest->getBody()->getContents(), true);
            return $content['sources'];

           } catch (RequestException $e) {
              //For handling exception
               echo Psr7\str($e->getRequest());
               if ($e->hasResponse()) {
                    echo Psr7\str($e->getResponse());
                }
          }
    }
}

Remember to change ADD_YOUR_API_KEY_HERE to the actual API_KEY received from News Api.

Step 7:

Next, create an ApiController file. Run this command on the terminal

php artisan make:controller ApiController

Open the newly created ApiController.php file located at news-app/app/Http/Controllers/ApiController.php and here we will do three things:

  1. Create a newsapi function to handle POST and GET requests. Read about HTTP Methods here.
  2. Set Cnn news to our default news source.
  3. Connect to our Api Method to retreive all news sources and selected source news.
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Api;

class ApiController extends Controller
{
    public function newsapi(Request $request)
    {
        if (($_SERVER["REQUEST_METHOD"] == "POST")){
        //Here is an example of the what will be received by POST 'al-jazeera-english : Al Jazeera English',
        //we need to split it up using a php function called exlpode(), explode() creates an array 'al-jazeera-english' is the source while 'Al Jazeera English' is the source name
        $source                   = $_POST['source'];
        $split_input              = explode(':', $source);
        $source                   = trim($split_input[0]); //trim() removes white spaces
        $data['source_name']      = $split_input[1];
        }
        if (empty($source)) {
            //Let us make `CNN` our default news source 
            $source                 = 'cnn';
            $data['source_name']    = 'CNN';
            $data['source_id']      = $source;
        }
      $api = new Api;
      $data['news']         = $api->getNews($source); // Passed  source id to our api model, to fetch news by the selected source
      $data['news_sources'] = $this->allSources(); //retrieve all news sources
      return view('welcome', $data);
    }

    public function allSources()
    {
      $api        = new Api;
      $allSources = $api->getAllSources(); //retrieve all news sources
      return $allSources;
    }
}

Step 8:

Edit the routes file: The routes file is located at news-app/routes/web.php. Here, we will do two things: 1. Create a GET route that connects to the ApiController, this loads our default view and it will load news from cnn. 2. Create a POST route that will pass the selected news source id to the ApiController. So when a user switches from cnn to Al-jazeera, this route will be used to pass the news source id .

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

// Route::get('/', function () {
//     return view('welcome');
// });
Route::get('/', 'ApiController@newsapi');
Route::post('/source_id', 'ApiController@newsapi');

Step 9:

Next, we navigate to our views folder and edit the contents of news-app/resources/views/welcome.blade.php with this:

    <!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>News Application with Laravel</title>
        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">

        <!-- Styles -->
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
        <!-- Latest compiled and minified CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

    </head>
    <body>
    <div id="appendDivNews">
        <nav class="navbar fixed-top navbar-light bg-faded" style="background-color: #e3f2fd;">
          <a class="navbar-brand" href="#">News Around the World</a>
        </nav>

            {{ csrf_field() }}
<section id="content" class="section-dropdown">
<p class="select-header"> Select a news source: </p>
<label class="select"> 
    <select name="news_sources" id="news_sources">
    <option value="{{@$source_id}} : {{@$source_name}}">{{$source_name}}</option>
    @foreach ($news_sources as $news_source)
      <option value="{{$news_source['id']}} : {{$news_source['name'] }}">{{$news_source['name']}}</option>
    @endforeach
    </select>
</label>

 </section> 
<p> News Source : {{$source_name}}</p>
    <section class="news">
    @foreach($news as $selected_news)
    <article>
        <img src="{{$selected_news['urlToImage']}}" alt="" />
        <div class="text">
            <h1>{{$selected_news['title']}}</h1>
            <p style="font-size: 14px">{{$selected_news['description']}} <a href="{{$selected_news['url']}}" target="_blank"><small>read more...</small></a> </p>
            <div style="padding-top: 5px;font-size: 12px">Author: {{$selected_news['author'] or "Unknown" }}</div>
            @if($selected_news['publishedAt'] != null)
             <div style="padding-top: 5px;">Date Published: {{ Carbon\Carbon::parse($selected_news['publishedAt'])->format('l jS \\of F Y ') }}</div>
             @else
             <div style="padding-top: 5px;">Date Published: Unknown</div>

             @endif

        </div>
    </article>
    @endforeach
</section>
</div>

         </body>
    <!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}"></script>

</html>

Step 10:

For our css file, open news-app/public/css/app.css, You might choose to clear out the current contents. I minified the css file, you can unminify it here.

*,.select:after{box-sizing:border-box}*{font-family:Roboto,sans-serif}body{margin:0;background:#333;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}.news{padding:10px;text-align:center;font-size:0}.news article{display:inline-block;max-width:400px;margin:10px;background:#eee;text-align:left;vertical-align:top;font-size:1rem;box-shadow:0 0 40px -10px #000;overflow:hidden}.news article img{width:100%;height:200px;-o-object-fit:cover;object-fit:cover;-webkit-box-reflect:below 0 linear-gradient(0deg,rgba(0,0,0,.5),transparent 50%)}.news article .text{padding:20px;color:#333}.news article .text h1{margin:0 0 .5em;font-weight:500}.news article .text p{margin:0 0 1em}.news article .text p:last-child{margin:0}@media (max-width:600px){.news{padding:0}.news article{display:block;max-width:100%;margin:0 0 20px}.news article:last-child{margin:0}}.section-dropdown{width:300px;display:block;margin:50px auto;text-align:center}.select,select{height:40px;width:240px}.select{border:1px solid #cacaca;overflow:hidden;position:relative;display:block}#loading,.select:after{height:100%;text-align:center}select{padding:5px;border:0;font-size:16px;-webkit-appearance:none;-moz-appearance:none;appearance:none}.select:after{content:"\f0dc";font-family:FontAwesome;color:#000;padding:12px 8px;position:absolute;right:0;top:0;background:#e3f2fd;z-index:1;width:10%;pointer-events:none}#loading{background-color:#fff;width:100%;position:fixed;z-index:9999}.select-header{text-align:left;padding-left:54px}

Step 11:

Finally, we edit our Javascript file. Navigate to news-app/public/js/app.js. You might choose to clear the contents of this file also.

$('select').on('change', function() {
  var source =  this.value ;  //gets the selected news source from the news source dropdown menu
  var _token = $('input[name="_token"]').val();

   $.ajax({
        type: "POST",
        url: "/source_id",
        data: { source: source, _token : _token }, //posts the selected option to our ApiController file
        success:function(result){
    // On success it gets `result`, which is a full html page that displays topnews from the news source selected.
          $('#appendDivNews').html(result);    // Append the html result to the div that has an id  of  `appendDivNews`
          },

        error:function(){
          alert("An error occoured, please try again!")
        }
    });

})


In other not to reload the page, Ajax was used to submit a POST request and the result received is a new page containing news details of the selected news source. The result was appended to the current view. with this, the page will not refresh.

And that is it, we have a full-fledged News Application. You can view/contribute to the codebase here. View the code live here.

This post was originally posted on Scotch.

Site Footer