Unread Notifications

Latest Notifications

No Notifications

You're all set! Start a discussion by leaving a comment on a lesson or replying to an existing comment.

AdonisJS 5 Infinite Load

Project Setup & Creating Dummy Data

9 MIN READ
11 months ago

In this lesson, we'll be setting up our project with fake data using a Model Factory and a Seeder. We'll also set up our initial list of posts so that we're all set and squared away to cover

Watch on YouTube

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

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.

  1. Select the project structure > Web

  2. Enter the project name > Your choice, I'll be using adonis-infinite-load-example

  3. Setup eslint? > Your choice, I'll be selecting no

  4. 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

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

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(),
})

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

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)
  }
}

Defining Our Post Model

Then, we'll need to also apply these columns to our Post Model

// app/Models/Post.ts

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
}

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.

// database/factories/index.ts

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()

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

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.

// database/seeders/Post.ts

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)
  }
}

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>

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.

// app/Controllers/Http/PostsController

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 })
  }
}

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.

// start/routes.ts

import Route from '@ioc:Adonis/Core/Route'

Route.get('/', async ({ view }) => {
  return view.render('welcome')
})

Route.get('/posts', 'PostsController.index').as('posts.index')

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

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.

Comment

Prepared By

Tom Gobich

Burlington, KY

Owner of Adocasts, JavaScript developer, educator, PlayStation gamer, burrito eater.

Visit Website