Typically, I wouldn't create a setup lesson for a topic like this, I'd just provide a repository that was already set up then specifically cover the topic at hand. However, since we haven't covered Lucid, Model Factories or Seeders at all in our Let's Learn Adonis 5 series, I thought it might be good to show the prep work here. If you're just interested in the infinite load portion, check out lesson 2 of this mini-series.
What We're Building
In this mini-series, we'll be building a page within our application that contains an infinite load for our post listing.
We'll start out by rendering an initial list using Adonis. We'll then use an IntersectionObserver
to be notified when our user reaches near the end of our list. Once they do, we'll call out to our API endpoint to fetch the next page of information. We'll utilize an Adonis component to have this rendered via our server to keep things clean. We'll then concatenate that next page onto our list. This will happen for each page we have available. Lastly, we'll notify our system we're done when we reach the last page of our paginated data.
Creating Our Project
First, we'll need to create our project. I'll be calling my project adonis-infinite-load-example
, feel free to name yours whatever you'd like.
So, let's run the following within our terminal to create our project.
npm init adonis-ts-app@latest adonis-infinite-load-example
Copied!
Replace adonis-infinite-load-example
with your project name.
Once we enter the above command to create our project Adonis is going to ask us a couple of questions to help us get up and running quicker.
Select the project structure > Web
Enter the project name > Your choice, I'll be using adonis-infinite-load-example
Setup eslint? > Your choice, I'll be selecting no
Configure webpack encore for compiling front-end assets? > Your choice, I'll be selecting no
From here Adonis will begin creating our project, then it'll install the needed dependencies based on our selections.
Installing & Configuring Lucid
Now that we have our project created, let's install and configure Lucid within our project. Lucid is Adonis' Object-Relational Mapping (ORM), it's going to be how we'll define our database schema to Adonis and be the mechanism we use to actually interact with our database.
Installing Lucid
We can install Lucid just like any other library within our application, by using NPM or Yarn to install it from @adonisjs/lucid
.
npm i @adonisjs/lucid
Copied!
Configuring Lucid
Now that we have Lucid installed within our project, we need to configure it within our project so that Adonis has all the info it needs to properly utilize Lucid.
Adonis makes configuring Adonis-specific libraries a breeze. We can utilize the Ace CLI, Adonis' command-line interface (CLI), to configure Lucid for us.
node ace configure @adonisjs/lucid
Copied!
This command will add configs, a database directory, Ace commands, environment variables, and more within our project.
Next, it's going to ask us which type of database we'll be using. Select whichever applies to you, I'll be using MySQL so I'll be selecting that.
Once everything is installed and configured it's going to have some instructions for us, and it'll specifically ask whether we want to view those in the browser or terminal. Select whichever you'd prefer.
Essentially, it's going to provide us with rules to add to our env.ts
file within our project. These will ensure that we have our database-specific environment variables defined. So, copy the appropriate rules for the database driver you selected and plop those within your env.ts
along with the other rules.
// env.ts import Env from '@ioc:Adonis/Core/Env' export default Env.rules({ HOST: Env.schema.string({ format: 'host' }), PORT: Env.schema.number(), APP_KEY: Env.schema.string(), APP_NAME: Env.schema.string(), CACHE_VIEWS: Env.schema.boolean(), SESSION_DRIVER: Env.schema.string(), NODE_ENV: Env.schema.enum(['development', 'production', 'testing'] as const), // my rules for mysql MYSQL_HOST: Env.schema.string({ format: 'host' }), MYSQL_PORT: Env.schema.number(), MYSQL_USER: Env.schema.string(), MYSQL_PASSWORD: Env.schema.string.optional(), MYSQL_DB_NAME: Env.schema.string(), })
Copied!
Configure Environment Variables
Lastly, we need to provide the correct values for the newly added environment variables for our database driver.
These values are what Lucid will use to connect to our database, so make sure they're correct.
# .env
DB_CONNECTION=mysql
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=
MYSQL_DB_NAME=lucid
Creating Our Model
Now that we have Lucid installed and configured within our project, we can utilize the new Ace CLI commands Lucid added to our project to quickly stub out a Model, Migration, and Controller for our posts.
We can utilize flags with the Ace CLI to do this all with a single command, so in your terminal let's run the following.
node ace make:model Post -mc
Copied!
Here's we're telling the Ace CLI to make a model called Post
. Then by adding on the flags m
and c
we're telling the Ace CLI to also make a migration and a controller for our Post model as well; m
for migration, and c
for controller.
Now that we have our Post Model created, we need to define and run our Post migration. Then, define our Post Model properties.
Defining Our Post Migration
We're going to keep things super simple for this mini-series by just having a title and a summary for our posts.
// database/migrations/[timestamp]_posts.ts import BaseSchema from '@ioc:Adonis/Lucid/Schema' export default class Posts extends BaseSchema { protected tableName = 'posts' public async up () { this.schema.createTable(this.tableName, (table) => { table.increments('id') table.string('title') table.string('summary') /** * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL */ table.timestamps(true, true) }) } public async down () { this.schema.dropTable(this.tableName) } }
Copied!
Defining Our Post Model
Then, we'll need to also apply these columns to our Post Model
import { DateTime } from 'luxon' import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm' export default class Post extends BaseModel { @column({ isPrimary: true }) public id: number @column() public title: string @column() public summary: string @column.dateTime({ autoCreate: true }) public createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime }
Copied!
- app
- Models
- Post.ts
Creating Fake Data Using A Model Factory & Seeder
In order to ensure our infinite load actually works, we're going to need plenty of data within our database to test with. Thankfully, we can make use of Model Factories and Seeders to create heaps of fake data very easily.
Model Factory
When we configured Lucid within our project, it provided us a stubbed factory file within our /database/factories
directory. I'll be using this file to create my Model Factory, but you're welcome to create a file specifically for this or a group of factories.
So, our factory is going to end up looking like the below.
import Factory from '@ioc:Adonis/Lucid/Factory' import Post from 'App/Models/Post' export const PostFactory = Factory .define(Post, ({ faker }) => { return { title: faker.lorem.words(6), summary: faker.lorem.lines(2) } }) .build()
Copied!
- database
- factories
- index.ts
Seeder
Next, let's create our Seeder. We can use the Ace CLI to do this, so let's run the following in our terminal.
node ace make:seeder Post
Copied!
Now within our newly generated Post seeder all we need to do is call our Post Model Factory's createMany
method. This method will automatically create however many records we tell it to within our database.
So, our Post seeder is going to end up looking like the below.
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder' import Post from 'App/Models/Post' import { PostFactory } from 'Database/factories' export default class PostSeeder extends BaseSeeder { public async run () { await PostFactory.createMany(100) } }
Copied!
- database
- seeders
- Post.ts
Now that our PostSeeder
is setup, let’s go ahead and run it so that our 100 posts are created. So, let’s run the following in our terminal.
node ace db:seed
Setting Up Our Initial Page
The last thing we'll do in this lesson in preparation for actually implementing our infinite load is to get our initial list of data rendering on our page.
Initial Page View
First, let's create our view within our /resources/views
directory. We'll create another directory called posts
and within we'll create a new edge file called index.edge
.
Since we don't need many styles, I'm just going to use inline styling for brevity's sake.
{{-- resources/views/posts/index.edge --}} <h1 style="padding: 2rem">Posts</h1> <div id="scroller" style="position: relative"> @each(post in page.rows) <div style="padding: 2rem"> <h3>{{ post.title }}</h3> <p>{{ post.summary }}</p> </div> @endeach </div>
Copied!
So here we're just looping through each post in our page. Note that since we're using the paginate method on our Post model, our page's posts are actually within page.rows
. We're also giving each post a padding of 2rem
so that we actually need to perform some scrolling on our initial list.
Route Handler
Next, let's add our route handling within our PostsController
, we'll specifically be using the index
method to handle this route. I'm going to go ahead and clear out the other resource methods to keep things easy to read for this mini-series.
Controller import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' import Post from 'App/Models/Post'; export default class PostsController { private perPage = 10 /** * Displays home page for posts * This is our initial list of 10 posts * @param param0 * @returns */ public async index ({ view }: HttpContextContract) { const page = await Post.query().paginate(1, this.perPage) return view.render('posts/index', { page }) } }
Copied!
- app
- Controllers
- Http
- Posts
First, we'll want to query for our posts. We can make use of our Post model's paginate
method to grab just the first page worth of our results. The first argument of paginate is the page we're on, so set this to 1
. The second argument is how many posts we'd like per page. Since I created 100 fake posts earlier, I'll keep things simple and have 10 posts per page.
Then, all we need to do is use the view
module to render out our posts/index.edge
page and pass it our first page of posts.
Define Our Route
All that's left to do is to define the route for our route handler so that we can actually reach the page. So, my routes file now looks like the following.
import Route from '@ioc:Adonis/Core/Route' Route.get('/', async ({ view }) => { return view.render('welcome') }) Route.get('/posts', 'PostsController.index').as('posts.index')
Copied!
- start
- routes.ts
So we've defined our page as /posts
, which will be accessible via http://localhost:3333/posts
. We've also defined that the index method inside our PostsController
should handle this route. Then lastly, we're just giving this route a name of posts.index
using the as
method.
Test Our Page
Fantastic, now let's start up our server and verify everything is working! In your terminal, let's run the following to start our server. Note that the --watch
will watch for changes so we don't need to restart our server on every change.
node ace serve --watch
Copied!
Once your server starts up, visit http://localhost:3333/posts within your browser and verify the page loads and that you can see your first page of posts a-okay.
Next Up
Now that we've done all the prep work and verified our initial page loads okay we're ready to implement our infinite load functionality. So, in the next lesson in this mini-series, we'll be focusing specifically on implementing our infinite load.
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!