Straightforward Pagination With Adonis JS

Straightforward Pagination With Adonis JS

·

10 min read

This article is for beginners, so if you already know what you are doing in Adonis JS, you may read on if you want and be kind enough to share it with some beginner.

In almost every application you build, you will need to render data to some page for the user to view or interact with, while these data may be just a picture, a few numbers, or a small string of text; some other times they are large amounts of data sets that it will not make sense to include all that data in one page. You just have to paginate it.

If you are new to Adonis JS and especially if you are coming from the Laravel world, you would expect to be able to add pagination to your view with just one method in Adonis JS like you can do in Laravel, I assure you however that this is not the case; you will need to do some work, howbeit not a tedious one.

Why am I writing this? because I was just where you are and I found almost nothing to help me, and when I did find some, they made me feel like this:

dizzy-tom.gif

So I would try to make it as easy as possible to understand.

Let's say we have a blog that has a hundred articles that we want our users to be able to read. Since loading one hundred articles at once may slow down our application, we will need to paginate it and give the user access to a small number of those articles on demand.

Create a new Adonis project:

adonis new pagination-tutorial

Change directory to your project

cd pagination-tutorial

Let's set up our database configuration in the .env file. We will be using MySQL for this tutorial.

HOST=127.0.0.1
PORT=3333
NODE_ENV=development
APP_URL=http://${HOST}:${PORT}
CACHE_VIEWS=false
APP_KEY=******************
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=pagination-tutorial
SESSION_DRIVER=cookie
HASH_DRIVER=bcrypt

Remember to create a database in MySQL with the corresponding database name; and install the MySQL driver to help us interact with the database.

npm i mysql

Ok, now that our database is set up, we need to set up our model and migration for the articles.

adonis make:model Article -m

The command above will create an Article.js file in the app/Models directory of our application and also create a migration file which you can find in the database/migrations directory.

You can use the model to do so many things, one of them is to establish relationships between tables; feel free to drop a request in the comment section if you would like be to write more about that. Now let us define our database schema:

class ArticleSchema extends Schema {
  up () {
    this.create('articles', (table) => {
      table.increments()
      table.string('article_title')
      table.text('article_body')
      table.string('article_image')
      table.timestamps()
    })
  }

  down () {
    this.drop('articles')
  }
}

You may already know what the table.string() method does but not the table.text() method; the table.text() method allows us to store much longer strings as opposed to 255 characters which the VARCHAR(table.string()) will allow. Now we can run our migration:

So we have our database schema setup and we can now run the command to set up our table in the database.

adonis migration:run

In an ideal situation, you would have data which either you or other authors have written, this data would normally be posted through a form to your backend and saved in your database but in this case, it will be too slow for us to post one hundred articles manually so we will be seeding the data.

Database seeding is a process of initializing an application with data similar to what we will be using in production so we can build faster. If you would like me to write about seeding data with Adonis JS please leave a comment requesting it and I will take it up.

We can create a seed file which will be found in the database/seeds/ directory using the following command:

adonis make:seed Article

In the database directory you have a factory.js file. We will use the factory file to define the blueprint for the data which we would like to seed into our database.

Factory.blueprint("App/Models/Article", (faker) => {
  return {
    article_title: faker.sentence({ words: 6 }),
    article_body: faker.paragraph(),
    article_image: faker.avatar({ fileExtension: "jpg" }),
  };
});

Next, we go back to our ArticleSeeder.js file to define how many articles will be created, we want to generate one hundred articles.

const Factory = use("Factory");

class ArticleSeeder {
  async run() {
    await Factory.model("App/Models/Article").createMany(100);
  }
}

module.exports = ArticleSeeder;

Once we have defines our requirements for seeding, we can now seed our database:

adonis seed

We have come a long way now, our database has been seeded and all we need to do is to fetch the data to our view and paginate it. However, to do that we need a controller, so let's generate one:

adonis make:controller Article

This command will create an article controller in app/Controllers/Http/ directory. Now, let's fetch our data and pass it to the view:

"use strict";

const Article = use("App/Models/Article");

class ArticleController {
  async getAllArticles({ params: { page }, response, view }) {
    try {
      const pageNumber = page || 1;

      const articles = await Article.query()
        .orderBy("id", "desc")
        .paginate(pageNumber, 10);

      return articles;

      return view.render("welcome", {
        articles: articles.toJSON(),
      });
    } catch (error) {
      console.log(error);

      session.flash({
        notification: {
          message: "Failed to fetch all articles",
        },
      });

      return response.redirect("back");
    }
  }
}

module.exports = ArticleController;

To understand what we are doing in the controller above, first, we need to understand the data that is being returned. Adonis returns an object that tells us the total number of items being sent, the number of items per page, the current page, the last page and an array of our actual data.

Please note that the object below has been adjusted so we can see the details of the data that we are to work with.

{
    "total": 100,
    "perPage": 1,
    "page": 1,
    "lastPage": 100,
    "data": [
        {
            "id": 100,
            "article_title": "Tu keclagjew accu ucok ga olegi.",
            "article_body": "Otasasik awoivu dewib sas evisogra fo ekumerav risi osoeguafo kihug femume noazzok hopsor et tag rah. Sa ipa vume kucefhu vusu fuheizi cofi fah pecir bimug na vuhehukej. Limep kel nukulus ujahofu amhu ijwuz eja ke sus ilo evudi pavalnur bikficat nasoj oreotuga ho fehumbir ga. Hezowbe tosifu tunev obnerpam tol bibbaveto cem mobjaro mikikim ulbo lojdifket cev.",
            "article_image": "//www.gravatar.com/avatar/fe37dabb40782da0c5b3f4116764c4ad.jpg",
            "created_at": "2020-09-25 12:37:30",
            "updated_at": "2020-09-25 12:37:30"
        }
    ]
}

So back to what we are doing in our controller. First, we get the page from our params object and we determine if any value is being returned otherwise we set pageNumber to 1.

After that, we fetch the articles ordered such that we get the latest article first. We also pass the pageNumber and the number of articles we want to receive on each page to the paginate method. After that, we just convert the data to JSON and pass it to our view.

To be able to receive the optional parameter, we need to add it to our route as shown below. The question mark after the parameter tells Adonis JS to treat the parameter as an optional parameter so it will not throw errors if there is no value present there. We have also added naming for our route .as("articles") to make it easier to reference. You can find the routes.js file in the start directory.

Route.get("/:page?", "ArticleController.getAllArticles").as("articles");

Now we can go to resources/welcome.edge (our edge template) to add our HTML. Here I am using Bootstrap cards.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Adonis Pagination Tutorial By Elvis Onobo | elvis.onobo@gmail.com</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>

<body>
  <h1 class="text-center">Page {{ articles.page }} of {{ articles.lastPage }}</h1>

  <div class="col-md-12 row justify-content-center">
    @each(article in articles.data)
      <div class="card m-2" style="width: 18rem;">
        <img src="{{ assetsUrl(article.article_image) }}" class="card-img-top" alt="{{ assetsUrl(article.article_title) }}">
        <div class="card-body">
          <h5 class="card-title">{{ assetsUrl(article.article_title) }}</h5>
          <p class="card-text">{{ assetsUrl(article.created_at) }}</p>
          <a href="#" class="btn btn-primary">Read Article</a>
        </div>
      </div>
    @endeach

    <div class="col-md-6 col-md-offset-3 row mt-2">
      <a href="{{ articles.page == 1 ? '#' : route('articles', { page: articles.page - 1}) }}" rel="prev" class="btn btn-primary">Previous</a>
      <p>Page {{ articles.page }} of {{ articles.lastPage }}</p>
      <a href="{{ articles.lastPage == articles.page ? '#' : route('articles', { page: articles.page + 1}) }}" class="btn btn-primary">Next</a>
    </div>
  </div>

</body>
</html>

So what are we doing here? We are iterating over our data to show the data from our database. Note that we are accessing articles.data and not articles for our iteration.

Outside our @each statement is where the magic happens. In our anchor tag, we check if articles.page is equal to one and if it is, then we do nothing. Otherwise if articles.page is greater than one, we subtract one from it to get the previous page we should go to; we then send this previous page to the route for the controller to use.

The exact opposite happens with the Next button. Here, we check if articles.page is equal to the articles.lastPage which means we are on the last page, if the current page is the last, then do nothing, otherwise we add one to the current page and pass it to our controller through the route.

That is it, your pagination is ready!

Thank you for reading this far. If you found this article informative in any way, please hit the like button or leave a comment to keep me going. Also, I would like if you share the article with your network and anyone you think it may help.

Let's connect on Twitter and on LinkedIn too.

You can find the code for this article on Github.

Cheers to your success!