How to build a Nodejs Website using Adonis and Tailwind CSS.

Create a new project

Using **npm init** or **yarn create**, you can start a new project. Let’s get started on our web project right away. I gave it the name **my-website**, but you’re free to give it your very own name.

npm init [email protected] my-website

If using yarn

yarn create adonis-ts-app my-website

Select **Web** when the process of installation prompts selections. We are creating a web application rather than an API. The rest of the parameters eslint/prettier configuration and webpack core configuration we set to false.

It will create a directory called **my-website** and load all the primary Laravel files there

Then we **cd** into my-website and run the command below:

“`

node ace serve --watch 

“`

The **serve** command launches the HTTP server and compiles TypeScript into JavaScript in memory.

The **–watch** flag is intended to view changes in the filesystem and automatically restart the server.

Visit **http://127.0.0.1:3333**, you should see the  welcome page of Adonis Framework similar to the image below :

Database Setup

After installing our AdonisJS application we will need to configure our database for it to work. Make sure you have a database configured and running on your computer. I will be using Mysql in this article.

I created a database with the name **db_my_website**.

We’ll then install a package to allow a database connection.

`npm i @adonisjs/lucid`

To use the configurator, you must first launch it.

node ace configure @adonisjs/lucid

The command lets you choose your database, establishes some essential information, and provides instructions on changing the settings. Follow the instructions on updating **env.ts** – add lines to the file below

 export default Env.rules({
   // ...
   DB_CONNECTION: Env.schema.string(),
 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(),
   // ...
 }); 

“`

 Open **.env** file in your project folder and change the below lines

“` 

 DB_CONNECTION=mysql
 MYSQL_HOST=localhost
 MYSQL_PORT=3306
 MYSQL_USER=root
 MYSQL_PASSWORD=
 MYSQL_DB_NAME=db_my_website 

“`

The above configuration instructs Adonis to connect to the MySQL Database named **db_my_website**, which is running on **localhost** port **3306** and is logged in as **root**.

But for Adonis to connect to the MySQL we need to install the Mysql Connector :

`npm install mysql –save`

# Understanding Adonis Architecture

AdonisJS is a framework that is largely based on the Model-View-Controller architecture. That means our Adonis application  uses a pattern consisting of three principal logical layers :

> Model handles everything database-related. Create data structure and define relationships.

> Controller builds logic for writing and collecting data from the model layer

> View represents the visualization of the data that the model contains

# Create Post Model, Migration, and Controller 

To create a new post, we’re going to need a Post model and Migration that represent the table  in our database, which we can create by running:

`node ace make: model Post -cm`

This will generate 3 files :

– **database/migrations/1626374691142_posts.ts** for the migration, represent table for our database.  

– **app/Models/Post.ts** as the Model

– **app/Controllers/Http/PostsController.ts** as the Controller 

If we open up the migration **1626374691142_posts.ts** file, we can add the following schema data to our migration:

 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.integer('user_id');
       table.string('title');
       table.text('body');
       table.string('image').nullable();
       table.string('slug').unique();
       //add created_at updated_at columns 
       table.timestamps(true, true) 
     })
   }
   public async down () {
     this.schema.dropTable(this.tableName)
   }
 } 

“`

We need to add also the following attributes in the **Post.ts** file

 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 user_id: number
   @column()
   public title:String
   @column()
   public body:String
   @column()
   public image:String
   @column()
   public slug:String
   @column.dateTime({ autoCreate: true })
   public createdAt: DateTime
   @column.dateTime({ autoCreate: true, autoUpdate: true })
   public updatedAt: DateTime
 } 

“`

 Open **app/Controllers/Http/PostsController.ts** file then let’s add some business logic for getting posts in the Index method.

 import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
 import Post from 'App/Models/Post';
 export default class PostsController {
   public async index (ctx: HttpContextContract) {
     const posts = await Post.all()
     return ctx.view.render('home', {posts})
   }
   public async create ({}: HttpContextContract) {
   }
   public async store ({}: HttpContextContract) {
   }
   public async show ({}: HttpContextContract) {
   }
   public async edit ({}: HttpContextContract) {
   }
   public async update ({}: HttpContextContract) {
   }
   public async destroy ({}: HttpContextContract) {
   }
 } 

“`

# Add Routes 

Add below routes to **start/routes.ts**.

“`

import Route from ‘@ioc:Adonis/Core/Route’

Route.get(“/”, “PostsController.index”);

“`

Database Seed

Database seeding is a way to set up your application with some initial data. We are going to seed our Posts table with the same data. First, let’s create our Seed.

`node ace make: seeder User`

open the generated database/seeders/Post.ts file and add the following code 

 import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'
 import Post from 'App/Models/Post'
 export default class PostSeeder extends BaseSeeder {
   public async run () {
     //Write your database queries inside the run method
     
            await Post.createMany([
       {
         title: 'How to build a website with  adonisJS and Tailwind css',
         body: "Lorem Ipsum is simply dummied text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
         image: "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2550&q=80"
       },
       {
         title: 'How to Learn Web Development,
         body : "is simply dummiedsimply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
         image: "https://images.unsplash.com/photo-1498050108023-c5249f4df085?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1352&q=80"
       }
       ,
       {
         title: 'Become a Web developer today',
         body : "Lorem Ipsum is simply dummied text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
         image: "https://images.unsplash.com/photo-1523800503107-5bc3ba2a6f81?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80"
       }
       ,
       {
         title: 'How to install nodejs',
         body : "Lorem Ipsum is simply dummied text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
         image: "https://images.unsplash.com/photo-1520085601670-ee14aa5fa3e8?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80"
       }
     ])
   }
   } 

 then run the seed with 

node ace db:seed

 This will insert those 4 posts in our posts table.

Create our App layout and Tailwind CSS

inside our app, we’ll create a new layout file located at **resources/views/layouts/main.edge**, with the following contents:

 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>{{ title }}</title>
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/1.9.6/tailwind.min.css">
 </head>
 <body>
     @!component('header')
     @!section('body')
 </body>
 </html> 

As you can see above, we have provided a link to the  **Tailwind CSS CDN URL**.

Create the Header component and add the following content

node ace make: view header
 <header class="text-gray-700 body-font border-b">
     <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
         <nav class="flex lg:w-2/5 flex-wrap items-center text-base md:ml-auto">
             <a href="/" class="mr-5 hover:text-gray-900">Home</a>
             <a href="/blog" class="mr-5 hover:text-gray-900">Blog</a>
             <a href="/about" class="mr-5 hover:text-gray-900">About</a>
         </nav>
         <a class="flex order-first lg:order-none lg:w-1/5 title-font font-bold items-center text-gray-900 lg:items-center lg:justify-center mb-4 md:mb-0">
             BLOG
         </a>
         <div class="lg:w-2/5 inline-flex lg:justify-end ml-5 lg:ml-0">
             <a href="#_" class="inline-flex items-center bg-gray-200 border-0 py-1 px-3 focus:outline-none hover:bg-gray-300 rounded text-base mt-4 md:mt-0">Login</a>
         </div>
     </div>
 </header> 

Render the view and you will end up with the following result

Display Post

node ace make:view home

Then we Create **resources/views/home.edge**  and paste the following markup 

 @layout('layouts/main')
 @set('title', 'Home page')
 @section('body')
 <div class="container mx-auto p-5">
   <h1 class="text-4xl mt-32 text-center tracking-tight leading-10 font-extrabold text-gray-900 sm:text-5xl sm:leading-none md:text-6xl">
       Welcome to The Blog
   </h1>
   <div class="mt-10  mx-auto">
       <section class="text-gray-600 body-font overflow-hidden">
         <div class="container px-5 py-24 mx-auto">
           <div class="flex flex-wrap -m-12">
             @each(post in posts)
               <div class="p-12 md:w-1/2 flex flex-col items-start">
                 <h2 class="sm:text-3xl text-2xl title-font font-medium text-gray-900 mt-4 mb-4">{{ post.title }}</h2>
                 <p class="leading-relaxed mb-8">{{ post.body }}</p>
                 <div class="flex items-center flex-wrap pb-4 mb-4 border-b-2 border-gray-100 mt-auto w-full">
                   <a class="text-indigo-500 inline-flex items-center">Learn More
                     <svg class="w-4 h-4 ml-2" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
                       <path d="M5 12h14"></path>
                       <path d="M12 5l7 7-7 7"></path>
                     </svg>
                   </a>
                 </div>
                 <a class="inline-flex items-center">
                   <img alt="blog" src="{{ post.image }}" class="w-12 h-12  flex-shrink-0 object-cover object-center">
                   <span class="flex-grow flex flex-col pl-4">
                     <span class="title-font font-medium text-gray-900">Holden Caulfield</span>
                     <span class="text-gray-400 text-xs tracking-widest mt-0.5">UI DEVELOPER</span>
                   </span>
                 </a>
               </div>
             @endeach
           </div>
         </div>
       </section>
   </div>
 </div>
 @end
 

Render the view and you will end up with the following result

Conlusion

With this article, we have learned how to create a Nodejs project from scratch using AdonisJS and Tailwind CSS, we used the example of a functional blog. This was just a basic example of how to set up a  Nodejs blog Website using AdonisJS and Tailwind CSS; however, there is so much more you could do. Then you could want to add login/auth and publish functionalities, as well as image feature upload so that each post can have a featured picture. You could also wish to have the user’s login before posting a post.

I hope you enjoyed this tutorial.

Manasse Ngudia
IT Consultant | Software Engineer | ML-DL-AIoT Enthusiast | DevOps I adore developing innovative solutions . I have built and contributed to countless projects from the ground up and the best product is the one that people utilize. I have recently worked on implementing a Smart AIoT system capable of keeping a maximum number of people in a queue and directing people to less congested queues, useful in the Manufacturing, retail, and transportation sectors.

Related Articles

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on Top - Get the daily news in your inbox