How to build a paginated blog using the DEV API
Tags: meta, php, blog, api
Published: 2019-11-28 15:49:14

In [the previous post in this series](https://dev.to/jamesrweb/programmatically-loading-posts-from-dev-to-onto-your-site-10je) we discussed how we could setup a clean implementation for interactive with [the DEV API](https://docs.dev.to/api/).

In this post we will now use [the `DevPostFetcher` class](https://dev.to/jamesrweb/programmatically-loading-posts-from-dev-to-onto-your-site-10je#the-devpostfetcher-class) that we implemented during the previous post to build a simple paginated MVC application. For this we will be using [`PHP`](https://www.php.net/), [`Composer` as our package manager](https://getcomposer.org/) and [`Flight-php` as our router](http://flightphp.com/).

In the next post in this series we will refactor our code from a one page script to a proper MVC architecture.

## Setting up


### Initialise our project files

Let's create our initially required files by running the following in the terminal:

```sh
# Mac/Linux
touch .htaccess composer.json index.php

# Windows without WSL/GitBash installed
echo "" | tee .htaccess composer.json index.php
```

### .HTACCESS

**Note:** The `.htaccess` is an apache server configuration file, if you prefer NGINX or another alternative, feel free to use that. Also, if you are following along in another language such as node, you can just ignore this file too.

Let's setup our `.htaccess` file by adding the following content to it:

```apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
```

This basically just says that if the request url is not an accessible file or directory, send the request to the index.php. Using `QSA` stands for "Query String Append" and tells the request that if any query parameters such as `?id=1` for example were passed in the original request, it should also send these parameters also to the index.php during the rewrite. The `L` flag stands for "Last" and tells the .htaccess to not use any other rewrite rules as this should be the last one processed.

### Project dependencies

Currently we only have one project dependency required and that is [`Flight`](http://flightphp.com/). To install Flight we will use [the `Composer`package manager](https://getcomposer.org/). This is a package manager for `PHP` just like `NPM` is for `Node` or `Nuget` is for `.NET`.

Firstly we need to install composer, to do this follow [the composer installation instructions](https://getcomposer.org/download/). Once this is done we can verify our installation by running `composer -V` in our terminal. If any errors occur, please refer back to the installation instructions.

To install Flight, all we need to do now is run:

```sh
composer require mikecao/flight
```

The `composer.json` should look like this:

```json
{
  "require": {
    "mikecao/flight": "^1.3"
  }
}
```

You will also have noticed that a `composer.lock` file and a `vendor` directory have been generated. The lock file tells composer how your dependencies work together and at what versions. The vendor folder is where all your dependencies live and an `autoloader` is provided, we will use this soon to be able to use our project dependencies.

### index.php

This is our applications entry point and where we will be working from now on. To get started, we should add the following to our index.php file:

```php
<?php

require_once __DIR__ . "/vendor/autoload.php";
```

This will require the `autoload` file generated when we required the Flight framework via Composer in the previous section. Now we are setup and ready to begin.

## Routing

Flight is a really simple but powerful micro framework for routing, you can build simple APIs all the way to MVC applications once you know how it works should you wish. I personally use Flight if I work on PHP services since it has a small footprint and provides an enjoyable experience to use.

To begin, in our `index.php` we can add the following lines to register our first route:

```php
use flight\Engine as Router;

$router = new Router();

$router->route("/", function(){
    echo "Hello world!";
});
```

This will call the `flight\Engine` namespace from the autoload file and allow us to use it with the name `Router`. The `$router` variable is an instance of a new `Router` and we register a route using the `route()` function on that instance. In this case, it will simply echo back the message "Hello world!" to us when we load the homepage. We can test this by running the following in our terminal:

```sh
php -S localhost:8080
```

This will run the built-in server from PHP itself on localhost:8080 and when we load that url, we can see our message: "Hello world!". Simple, right?

## Posts routing

Now we understand the very basic usage of Flight we can begin building out our `posts` routes. In our `index.php` lets add a new `/posts` route:

```php
$router->route("/posts", function() {
    $request = Flight::request();
    $devPosts = new DevPostFetcher("your_api_key");
    if(isset($request->query["page"])) {
        $page = (int) $request->query["page"];
        $page = $page !== 0 ? $page : 1;
        $devPosts->setPage($page);
    }
    $posts = $devPosts->fetch();
    print_r($posts);
});
```

This registers the `/posts` route, uses the global `request` static method on the `Flight` constant afforded to us by the Flight library. Checks if there is a query parameter called `page` and if there is, cast it to an integer and if the integer casting comes back as 0, we know it wasn't a number set as the `page` parameter and thus we default to 1, otherwise we use the page number provided.

This won't work yet however as we don't currently have access to the DevPostFetcher class. To use this we should create another file in the project root called `DevPostFetcher.php` and paste in [the `DevPostFetcher` class](https://dev.to/jamesrweb/programmatically-loading-posts-from-dev-to-onto-your-site-10je#the-devpostfetcher-class) but I advise reading the previous article before continuing this post as it offers some insight into the workings of the class and also some information of issues with local development and SSL.

Once you have created the `DevPostFetcher.php` file and added the class into it, we just need to do one more thing and that is to require that file into our `index.php`, we can do this by adding the following below our previous `require_once` statement:

```php
require_once __DIR__ . "/DevPostFetcher.php";
```

Now go to `localhost:8080/posts` and you should see a JSON dump of your Dev.to posts appearing. If you have any authorisation or SSL errors, ensure you have set things up correctly as outlined in [the previous post in this series](https://dev.to/jamesrweb/programmatically-loading-posts-from-dev-to-onto-your-site-10je).

## Pagination

Pagination is now really simple since we just need to pass a query parameter calles `page` with a valid number, for example: `/posts?page=3` which will return, by default, post number 20 up to 30. If no posts exist on page 3 however, for example if we only have 10 posts, ofcourse any page above page 1 by default won't return anything. In this case, we can do whatever we like from a redirect to `/posts` to show page 1 or we could show a special UI for this case or we can even just throw an error. For me, I will build a custom UI for this case and in the next section we will add a simple UI for our posts list.

## Building the user interface

Flight makes rendering views a doddle. First we create a `views` directory and add a `layout.php`, `no_posts.php` and `posts.php` inside it.

In the `layout.php` add the following content:

```php
<html>
  <head>
    <title><?php echo $page_title ?></title>
  </head>
  <body>
    <?php echo $body_content ?>
  </body>
</html>
```

In the no_posts.php add the following content:

```php
<p>No posts found!</p>
```

Finally, in the posts.php add the following content:

```php
<ul>
  <?php foreach($posts as $post): ?>
    <li>
      <a href="<?php echo $post['url']; ?>">
        <?php echo $post['title']; ?>
      </a>
    </li>
  <?php endforeach; ?>
</ul>
```

Then we can change our `/posts` route to look like so:

```php
$router->route("/posts", function() {
    $request = Flight::request();
    $devPosts = new DevPostFetcher("your_api_key");
    if(isset($request->query["page"])) {
        $page = (int) $request->query["page"];
        $page = $page !== 0 ? $page : 1;
        $devPosts->setPage($page);
    }
    $posts = $devPosts->fetch();

    if(count($posts) < 1) {
      Flight::render('no_posts', [], 'body_content');
    } else {
      Flight::render('posts', ['posts' => $posts], 'body_content');
    }

    Flight::render('layout', ['page_title' => "Posts"]);
});
```

This will check if any posts were returned for the page requested and if there weren't any, we render the `no_posts` template into the `body_content` variable. If posts were returned, we render the `posts` template into the `body_content` variable. Either condition that is met to generate the `body_content` variable will be rendered into the `layout` file.

## Conclusions

In this post we setup a very simple paginated blog using [`PHP`](https://www.php.net/), [`Composer`](https://getcomposer.org/) and [`Flight`](http://flightphp.com/). In the next post we will tidy up the codebase and refactor our setup to follow less of a "scratch pad" approach and use a more traditional MVC structure.