Pagination

Lucid has inbuilt support for offset based pagination. You can paginate the results of a query by chaining the .paginate method.

The paginate method accepts the page number as the first argument and the number of rows to fetch as the second argument. Internally, we execute an additional query to count the total number of rows.

const page = request.input('page', 1)
const limit = 10
const posts = await Database.from('posts').paginate(page, limit)
console.log(posts)

The paginate method returns an instance of the SimplePaginatorClass . It holds the meta data for the pagination, alongside the fetched rows.

SimplePaginator {
rows: [
{
// ...
},
{
// ...
},
],
totalNumber: '50',
perPage: 10,
currentPage: 1,
qs: {},
url: '/',
firstPage: 1,
isEmpty: false,
total: 50,
hasTotal: true,
lastPage: 5,
hasMorePages: true,
hasPages: true
}

Following is a complete example of displaying the pagination links inside the edge template.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Database from '@ioc:Adonis/Lucid/Database'
class PostsController {
public async index ({ request, view }: HttpContextContract) {
const page = request.input('page', 1)
const limit = 10
const posts = await Database.from('posts').paginate(page, limit)
// Changes the baseURL for the pagination links
posts.baseUrl('/posts')
return view.render('posts/index', { posts })
}
}

Open the posts/index.edge file and paste the following code snippet inside it.

<div>
@each(post in posts.rows)
<h1>{{ post.title }}</h1>
<p> {{ excerpt(post.body, 200) }} </p>
@endeach
</div>
<hr>
<div>
@each(anchor in posts.getUrlsForRange(1, posts.lastPage))
<a href="{{ anchor.url }}">
{{ anchor.page }}
</a>
@endeach
</div>

The getUrlsForRange method accepts a range of pages and returns an array of objects with the following properties.

[
{
url: '/?page=1',
page: 1,
isActive: true,
isSeperator: false,
},
{
url: '/?page=2',
page: 2,
isActive: true,
isSeperator: false,
},
// ...
]

Serializing to JSON

You can also serialize the paginator results to JSON by calling the toJSON method. It returns the key names in snake_case by default. However, you can pass a naming strategy to override the default convention.

const posts = await Database.from('posts').paginate(page, limit)
return posts.toJSON()
{
"meta": {
"total": 50,
"per_page": 5,
"current_page": 1,
"last_page": 10,
"first_page": 1,
"first_page_url": "/?page=1",
"last_page_url": "/?page=10",
"next_page_url": "/?page=2",
"previous_page_url": null
},
"data": []
}

In the following example, we override the naming strategy to return keys in camelCase.

const posts = await Database.from('posts').paginate(page, limit)
posts.namingStrategy = {
paginationMetaKeys() {
return {
total: 'total',
perPage: 'perPage',
currentPage: 'currentPage',
lastPage: 'lastPage',
firstPage: 'firstPage',
firstPageUrl: 'firstPageUrl',
lastPageUrl: 'lastPageUrl',
nextPageUrl: 'nextPageUrl',
previousPageUrl: 'previousPageUrl',
}
}
}
return posts.toJSON()

The naming strategy can also be assigned to the SimplePaginator class constructor to override it globally. The following code must go inside a provider or a preload file .

import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
constructor(protected app: ApplicationContract) {}
public async ready() {
const Db = this.app.container.use('Adonis/Lucid/Database')
Db.SimplePaginator.namingStrategy = {
paginationMetaKeys() {
return {
// ... same as above
}
}
}
}
}