One place for hosting & domains

      Application

      How To Add Sidekiq and Redis to a Ruby on Rails Application


      Introduction

      When developing a Ruby on Rails application, you may find you have application tasks that should be performed asynchronously. Processing data, sending batch emails, or interacting with external APIs are all examples of work that can be done asynchronously with background jobs. Using background jobs can improve your application’s performance by offloading potentially time-intensive tasks to a background processing queue, freeing up the original request/response cycle.

      Sidekiq is one of the more widely used background job frameworks that you can implement in a Rails application. It is backed by Redis, an in-memory key-value store known for its flexibility and performance. Sidekiq uses Redis as a job management store to process thousands of jobs per second.

      In this tutorial, you will add Redis and Sidekiq to an existing Rails application. You will create a set of Sidekiq worker classes and methods to handle:

      • A batch upload of endangered shark information to the application database from a CSV file in the project repository.
      • The removal of this data.

      When you are finished, you will have a demo application that uses workers and jobs to process tasks asynchronously. This will be a good foundation for you to add workers and jobs to your own application, using this tutorial as a jumping off point.

      Prerequisites

      To follow this tutorial, you will need:

      Step 1 — Cloning the Project and Installing Dependencies

      Our first step will be to clone the rails-bootstrap repository from the DigitalOcean Community GitHub account. This repository includes the code from the setup described in How To Add Bootstrap to a Ruby on Rails Application, which explains how to add Bootstrap to an existing Rails 5 project.

      Clone the repository into a directory called rails-sidekiq:

      • git clone https://github.com/do-community/rails-bootstrap.git rails-sidekiq

      Navigate to the rails-sidekiq directory:

      In order to work with the code, you will first need to install the project’s dependencies, which are listed in its Gemfile. You will also need to add the sidekiq gem to the project to work with Sidekiq and Redis.

      Open the project’s Gemfile for editing, using nano or your favorite editor:

      Add the gem anywhere in the main project dependencies (above development dependencies):

      ~/rails-sidekiq/Gemfile

      . . . 
      # Reduces boot times through caching; required in config/boot.rb
      gem 'bootsnap', '>= 1.1.0', require: false
      gem 'sidekiq', '~>6.0.0'
      
      group :development, :test do
      . . .
      

      Save and close the file when you are finished adding the gem.

      Use the following command to install the gems:

      You will see in the output that the redis gem is also installed as a requirement for sidekiq.

      Next, you will install your Yarn dependencies. Because this Rails 5 project has been modified to serve assets with webpack, its JavaScript dependencies are now managed by Yarn. This means that it’s necessary to install and verify the dependencies listed in the project’s package.json file.

      Run yarn install to install these dependencies:

      Next, run your database migrations:

      Once your migrations have finished, you can test the application to ensure that it is working as expected. Start your server in the context of your local bundle with the following command if you are working locally:

      If you are working on a development server, you can start the application with:

      • bundle exec rails s --binding=your_server_ip

      Navigate to localhost:3000 or http://your_server_ip:3000. You will see the following landing page:

      Application Landing Page

      To create a new shark, click on the Get Shark Info button, which will take you to the sharks/index route:

      Sharks Index Route

      To verify that the application is working, we can add some demo information to it. Click on New Shark. You will be prompted for a username (sammy) and password (shark), thanks to the project’s authentication settings.

      On the New Shark page, input “Great White” into the Name field and “Scary” into the Facts field:

      Shark Create

      Click on the Create Shark button to create the shark. Once you see that your shark has been created, you can kill the server with CTRL+C.

      You have now installed the necessary dependencies for your project and tested its functionality. Next, you can make a few changes to the Rails application to work with your endangered sharks resources.

      Step 2 — Generating a Controller for Endangered Shark Resources

      To work with our endangered shark resources, we will add a new model to the application and a controller that will control how information about endangered sharks is presented to users. Our ultimate goal is to make it possible for users to upload a large batch of information about endangered sharks without blocking our application’s overall functionality, and to delete that information when they no longer need it.

      First, let’s create an Endangered model for our endangered sharks. We’ll include a string field in our database table for the shark name, and another string field for the International Union for the Conservation of Nature (IUCN) categories that determine the degree to which each shark is at risk.

      Ultimately, our model structure will match the columns in the CSV file that we will use to create our batch upload. This file is located in the db directory, and you can check its contents with the following command:

      The file contains a list of 73 endangered sharks and their IUCN statuses - vu for vulnerable, en for endangered, and cr for critically endangered.

      Our Endangered model will correlate with this data, allowing us to create new Endangered instances from this CSV file. Create the model with the following command:

      • rails generate model Endangered name:string iucn:string

      Next, generate an Endangered controller with an index action:

      • rails generate controller endangered index

      This will give us a starting point to build out our application’s functionality, though we will also need to add custom methods to the controller file that Rails has generated for us.

      Open that file now:

      • nano app/controllers/endangered_controller.rb

      Rails has provided us with a skeletal outline that we can begin to fill in.

      First, we’ll need to determine what routes we require to work with our data. Thanks to the generate controller command, we have an index method to begin with. This will correlate to an index view, where we will present users with the option to upload endangered sharks.

      However, we will also want to deal with cases where users may have already uploaded the sharks; they will not need an upload option in this case. We will somehow need to assess how many instances of the Endangered class already exist, since more than one indicates that the batch upload has already occurred.

      Let’s start by creating a set_endangered private method that will grab each instance of our Endangered class from the database. Add the following code to the file:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index
        end
      
        private
      
          def set_endangered
            @endangered = Endangered.all 
          end
      
      end
      

      Note that the before_action filter will ensure that the value of @endangered is only set for the index and data routes, which will be where we handle the endangered shark data.

      Next, add the following code to the index method to determine the correct path for users visiting this part of the application:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      . . . 
      

      If there are more than 0 instances of our Endangered class, we will redirect users to the data route, where they can view information about the sharks they’ve created. Otherwise, they will see the index view.

      Next, below the index method, add a data method, which will correlate to a data view:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . . 
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      
        def data 
        end
      . . .
      

      Next, we will add a method to handle the data upload itself. We’ll call this method upload, and it will call a Sidekiq worker class and method to perform the data upload from the CSV file. We will create the definition for this worker class, AddEndangeredWorker, in the next step.

      For now, add the following code to the file to call the Sidekiq worker to perform the upload:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . . 
        def data 
        end
      
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      . . .
      

      By calling the perform_async method on the AddEndangeredWorker class, using the CSV file as an argument, this code ensures that the shark data and upload job get passed to Redis. The Sidekiq workers that we will set up monitor the job queue and will respond when new jobs arise.

      After calling perform_async, our upload method redirects to the data path, where users will be able to see the uploaded sharks.

      Next, we’ll add a destroy method to destroy the data. Add the following code below the upload method:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . . 
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      
        def destroy
          RemoveEndangeredWorker.perform_async
          redirect_to root_path
        end
      . . . 
      

      Like our upload method, our destroy method includes a perform_async call on a RemoveEndangeredWorker class – the other Sidekiq worker that we will create. After calling this method, it redirects users to the root application path.

      The finished file will look like this:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      
        def data 
        end
      
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      
        def destroy
          RemoveEndangeredWorker.perform_async
          redirect_to root_path
        end
      
        private
      
          def set_endangered
            @endangered = Endangered.all 
          end 
      
      end
      

      Save and close the file when you are finished editing.

      As a final step in solidifying our application’s routes, we will modify the code in config/routes.rb, the file where our route declarations live.

      Open that file now:

      The file currently looks like this:

      ~/rails-sidekiq/config/routes.rb

      Rails.application.routes.draw do
        get 'endangered/index'
        get 'home/index'
        resources :sharks do
                resources :posts
        end
        root 'home#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      We will need to update the file to include the routes that we’ve defined in our controller: data, upload, and destroy. Our data route will match with a GET request to retrieve the shark data, while our upload and destroy routes will map to POST requests that upload and destroy that data.

      Add the following code to the file to define these routes:

      ~/rails-sidekiq/config/routes.rb

      Rails.application.routes.draw do
        get 'endangered/index'
        get 'endangered/data', to: 'endangered#data'
        post 'endangered/upload', to: 'endangered#upload'
        post 'endangered/destroy', to: 'endangered#destroy'
        get 'home/index'
        resources :sharks do
                resources :posts
        end
        root 'home#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Save and close the file when you are finished editing.

      With your Endangered model and controller in place, you can now move on to defining your Sidekiq worker classes.

      Step 3 — Defining Sidekiq Workers

      We have called perform_async methods on our Sidekiq workers in our controller, but we still need to create the workers themselves.

      First, create a workers directory for the workers:

      Open a file for the AddEndangeredWorker worker:

      • nano app/workers/add_endangered_worker.rb

      In this file, we will add code that will allow us to work with the data in our CSV file. First, add code to the file that will create the class, include the Ruby CSV library, and ensure that this class functions as a Sidekiq Worker:

      ~/rails-sidekiq/app/workers/add_endangered_worker.rb

      class AddEndangeredWorker
        require 'csv'
        include Sidekiq::Worker
        sidekiq_options retry: false
      
      end
      

      We’re also including the retry: false option to ensure that Sidekiq does not retry the upload in the case of failure.

      Next, add the code for the perform function:

      ~/rails-sidekiq/app/workers/add_endangered_worker.rb

      class AddEndangeredWorker
        require 'csv'
        include Sidekiq::Worker
        sidekiq_options retry: false
      
        def perform(csv_file)
          CSV.foreach(csv_file, headers: true) do |shark|
          Endangered.create(name: shark[0], iucn: shark[1])
        end
       end
      
      end
      

      The perform method receives arguments from the perform_async method defined in the controller, so it’s important that the argument values are aligned. Here, we pass in csv_file, the variable we defined in the controller, and we use the foreach method from the CSV library to read the values in the file. Setting headers: true for this loop ensures that the first row of the file is treated as a row of headers.

      The block then reads the values from the file into the columns we set for our Endangered model: name and iucn. Running this loop will create Endangered instances for each of the entries in our CSV file.

      Once you have finished editing, save and close the file.

      Next, we will create a worker to handle deleting this data. Open a file for the RemoveEndangeredWorker class:

      • nano app/workers/remove_endangered_worker.rb

      Add the code to define the class, and to ensure that it uses the CSV library and functions as a Sidekiq Worker:

      ~/rails-sidekiq/app/workers/remove_endangered_worker.rb

      class RemoveEndangeredWorker
        include Sidekiq::Worker
        sidekiq_options retry: false
      
      end
      

      Next, add a perform method to handle the destruction of the endangered shark data:

      ~/rails-sidekiq/app/workers/remove_endangered_worker.rb

      class RemoveEndangeredWorker
        include Sidekiq::Worker
        sidekiq_options retry: false
      
        def perform
          Endangered.destroy_all
        end
      
      end
      

      The perform method calls destroy_all on the Endangered class, which will remove all instances of the class from the database.

      Save and close the file when you are finished editing.

      With your workers in place, you can move on to creating a layout for your endangered views, and templates for your index and data views, so that users can upload and view endangered sharks.

      Step 4 — Adding Layouts and View Templates

      In order for users to enjoy their endangered shark information, we will need to address two things: the layout for the views defined in our endangered controller, and the view templates for the index and data views.

      Currently, our application makes use of an application-wide layout, located at app/views/layouts/application.html.erb, a navigation partial, and a layout for sharks views. The application layout checks for a content block, which allows us to load different layouts based on which part of the application our user is engaging with: for the home index page, they will see one layout, and for any views relating to individual sharks, they will see another.

      We can repurpose the sharks layout for our endangered views since this format will also work for presenting shark data in bulk.

      Copy the sharks layout file over to create an endangered layout:

      • cp app/views/layouts/sharks.html.erb app/views/layouts/endangered.html.erb

      Next, we’ll work on creating the view templates for our index and data views.

      Open the index template first:

      • nano app/views/endangered/index.html.erb

      Delete the boilerplate code and add the following code instead, which will give users some general information about the endangered categories and present them with the option to upload information about endangered sharks:

      ~/rails-sidekiq/app/views/endangered/index.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Endangered Sharks</h1>
      
      <p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
      
      <br>
      
        <%= form_tag endangered_upload_path do %>
        <%= submit_tag "Import Endangered Sharks" %>
        <% end %>
      
        <br>
      
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      A form_tag makes the data upload possible by pointing a post action to the endangered_upload_path – the route we defined for our uploads. A submit button, created with the submit_tag, prompts users to "Import Endangered Sharks".

      In addition to this code, we’ve included some general information about ICUN codes, so that users can interpret the data they will see.

      Save and close the file when you are finished editing.

      Next, open a file for the data view:

      • nano app/views/endangered/data.html.erb

      Add the following code, which will add a table with the endangered shark data:

      ~/rails-sidekiq/app/views/endangered/data.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Endangered Sharks</h1>
      
      <p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
      
      <div class="table-responsive">
      <table class="table table-striped table-dark">
        <thead>
          <tr>
            <th>Name</th>
            <th>IUCN Status</th>
            <th colspan="3"></th>
          </tr>
        </thead>
      
        <tbody>
          <% @endangered.each do |shark| %>
            <tr>
              <td><%= shark.name %></td>
              <td><%= shark.iucn %></td>
            </tr>
          <% end %>
        </tbody>
      </table>
      </div>
      
      <br>
      
        <%= form_tag endangered_destroy_path do %>
        <%= submit_tag "Delete Endangered Sharks" %>
        <% end %>
      
        <br>
      
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      This code includes the ICUN status codes once again, and a Bootstrap table for the outputted data. By looping through our @endangered variable, we output the name and ICUN status of each shark to the table.

      Below the table, we have another set of form_tags and submit_tags, which post to the destroy path by offering users the option to "Delete Endangered Sharks".

      Save and close the file when you are finished editing.

      The last modification we’ll make to our views will be in the index view associated with our home controller. You may recall that this view is set as the root of the application in config/routes.rb.

      Open this file for editing:

      • nano app/views/home/index.html.erb

      Find the column in the row that states Sharks are ancient:

      ~/rails-sidekiq/app/views/home/index.html.erb

      . . . 
              <div class="col-lg-6">
                  <h3>Sharks are ancient</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                  </p>
              </div>
          </div>
      </div>
      

      Add the following code to the file:

      ~/rails-sidekiq/app/views/home/index.html.erb

      . . . 
              <div class="col-lg-6">
                  <h3>Sharks are ancient and SOME are in danger</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago. Without our help, some could disappear soon.</p>
                  <p><%= button_to 'Which Sharks Are in Danger?', endangered_index_path, :method => :get,  :class => "btn btn-primary btn-sm"%>
                  </p>
              </div>
          </div>
      </div>
      

      We’ve included a call to action for users to learn more about endangered sharks, by first sharing a strong message, and then adding a button_to helper that submits a GET request to our endangered index route, giving users access to that part of the application. From there, they will be able to upload and view endangered shark information.

      Save and close the file when you are finished editing.

      With your code in place, you are ready to start the application and upload some sharks!

      Step 5 — Starting Sidekiq and Testing the Application

      Before we start the application, we’ll need to run migrations on our database and start Sidekiq to enable our workers. Redis should already be running on the server, but we can check to be sure. With all of these things in place, we’ll be ready to test the application.

      First, check that Redis is running:

      You should see output like the following:

      Output

      ● redis-server.service - Advanced key-value store Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2019-11-12 20:37:13 UTC; 1 weeks 0 days ago

      Next, run your database migrations:

      You can now start Sidekiq in the context of your current project bundle by using the bundle exec sidekiq command:

      You will see output like this, indicating that Sidekiq is ready to process jobs:

      Output

      m, `$b .ss, $$: .,d$ `$$P,d$P' .,md$P"' ,$$$$$b/md$$$P^' .d$$$$$$/$$$P' $$^' `"/$$$' ____ _ _ _ _ $: ,$$: / ___|(_) __| | ___| | _(_) __ _ `b :$$ ___ | |/ _` |/ _ |/ / |/ _` | $$: ___) | | (_| | __/ <| | (_| | $$ |____/|_|__,_|___|_|__|__, | .d$$ |_| 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux] 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: See LICENSE and the LGPL-3.0 for licensing details. 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Booting Sidekiq 6.0.3 with redis options {:id=>"Sidekiq-server-PID-17621", :url=>nil} 2019-11-19T21:43:00.543Z pid=17621 tid=gpiqiesdl INFO: Starting processing, hit Ctrl-C to stop

      Open a second terminal window, navigate to the rails-sidekiq directory, and start your application server.

      If you are running the application locally, use the following command:

      If you are working with a development server, run the following:

      • bundle exec rails s --binding=your_server_ip

      Navigate to localhost:3000 or http://your_server_ip:3000 in the browser. You will see the following landing page:

      Sidekiq App Home

      Click on the Which Sharks Are in Danger? button. Since you have not uploaded any endangered sharks, this will take you to the endangered index view:

      Endangered Index View

      Click on Import Endangered Sharks to import the sharks. You will see a status message telling you that the sharks have been imported:

      Begin Import

      You will also see the beginning of the import. Refresh your page to see the entire table:

      Refresh Table

      Thanks to Sidekiq, our large batch upload of endangered sharks has succeeded without locking up the browser or interfering with other application functionality.

      Click on the Home button at the bottom of the page, which will bring you back to the application main page:

      Sidekiq App Home

      From here, click on Which Sharks Are in Danger? again. This will now take you directly to the data view, since you already uploaded the sharks.

      To test the delete functionality, click on the Delete Endangered Sharks button below the table. You should be redirected to the home application page once again. Clicking on Which Sharks Are in Danger? one last time will take you back to the index view, where you will have the option to upload sharks again:

      Endangered Index View

      Your application is now running with Sidekiq workers in place, which are ready to process jobs and ensure that users have a good experience working with your application.

      Conclusion

      You now have a working Rails application with Sidekiq enabled, which will allow you to offload costly operations to a job queue managed by Sidekiq and backed by Redis. This will allow you to improve your site’s speed and functionality as you develop.

      If you would like to learn more about Sidekiq, the docs are a good place to start.

      To learn more about Redis, check out our library of Redis resources. You can also learn more about running a managed Redis cluster on DigitalOcean by looking at the product documentation.



      Source link

      How To Build an Inspirational Quote Application Using AdonisJs and MySQL


      The author selected the Tech Education Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      AdonisJs is a Node.js web framework written in plain JavaScript that runs on all major operating systems. It uses the popular MVC (Model – View – Controller) design pattern and offers a stable ecosystem for writing server-side web applications. The framework features seamless authentication, SQL ORM (object-relational mapping), migrations, and database seeding. AdonisJs has a similar architecture to the PHP web application framework Laravel, including the same folder structure and several shared setup concepts.

      By default, AdonisJs uses the Edge template engine that is designed for intuitive use. Just like Laravel, AdonisJs ships with an ORM called Lucid that serves as an interface for communication between an application’s models and the database. With AdonisJs, developers can build a full-stack application where the back-end server will be responsible for applying the business logic, routing, and rendering all the pages for the application. It is also possible to create a web service API to return JSON responses from a controller; these web services can then be consumed using front-end frameworks such as Vue.js, React, and Angular.

      In this tutorial, you’ll build an application with AdonisJs using its CLI. You’ll create routes, controllers, models, and views within your application and you’ll carry out form validations. The example in this tutorial will be an inspirational quote application in which a user can sign up and log in to create an inspirational quote. This demo application will give you the opportunity to carry out CRUD (Create, Read, Update, and Delete) operations.

      Prerequisites

      Before you begin this guide, you will need the following:

      Note: This tutorial uses a macOS machine for development. If you’re using another operating system, you may need to use sudo for npm commands in the first steps.

      Step 1 — Installing the Adonis CLI

      In this section, you will install Adonis CLI and all its required packages on your local machine. The CLI will allow you to scaffold a new AdonisJs project as well as create and generate boilerplate for controllers, middlewares, and models in your application. You’ll also create your database for the project.

      Run the following command to install the AdonisJs CLI globally on your machine via npm:

      Once the installation process is complete, type the following command in the terminal to confirm the installation of AdonisJs and view the current version:

      You will see output showing the current version of AdonisJs:

      Output

      4.1.0

      With the successful installation of the AdonisJs CLI, you now have access to and can use the adonis command to create fresh installations of an AdonisJs project, manage your project, and generate relevant files such as the controllers, models, etc.

      Now, you can proceed to create a new AdonisJs project by using the adonis command as shown here:

      • adonis new adonis-quotes-app

      The preceding command will create an application named adonis-quotes-app in a new directory with the same name in your local project directory with the relevant AdonisJs MVC structure.

      Move into the new application folder:

      Then start your application by running:

      This will start the development server on the default port 3333 as specified inside the root .env file for your application. Navigate to http://localhost:3333 to view the welcome page of AdonisJs.

      Welcome page of AdonisJs

      Now you’ll complete the setup of your database. Here, you’ll install the mysql driver to connect to your MySQL server from your Node.js application via npm. To begin, go back to your terminal where the application is currently running, stop the process with CTRL + C and run the following command:

      Now that you have successfully installed the MySQL Node.js driver for this application, you need to create the application database and set up the appropriate connection to it.

      The latest version of MySQL that you have installed from the prerequisite tutorial uses a default authentication plugin named caching_sha2_password. This is currently not supported by Node.js drivers for MySQL. To avoid any database connection issue from your application, you will need to create a new MySQL user and use the currently supported authentication plugin—mysql_native_password.

      To begin, access the MySQL client using the root account:

      You will be prompted to enter your root account password set up during the MySQL installation.

      Next, create the user and password using the mysql_native_password plugin:

      • CREATE USER 'sammy'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

      You will see the following output:

      Output

      Query OK, 0 rows affected (0.02 sec)

      Next, create a database for the application with:

      You will see the following output:

      Output

      Query OK, 1 row affected (0.03 sec)

      You’ve now successfully created the database for this application.

      Now, enable access to the created database for the new MySQL user. Run the following command to grant all privileges to the user in the database:

      • GRANT ALL PRIVILEGES ON adonis.* TO 'sammy'@'localhost';

      Reload the grant tables by running the following command to apply the changes that you just made:

      You will see the following output:

      Output

      Query OK, 0 rows affected (0.00 sec)

      Exit the MySQL client with:

      You’ve successfully installed the AdonisJs CLI, created a new AdonisJs project, and installed mysql via npm. You also created the database for this application and set up a MySQL user with the appropriate privileges to it. This is the basic setup for your application and in the next section you will begin to create the necessary views for your application.

      Step 2 — Using the Edge Templating Engine

      AdonisJs is shipped with its own template engine called Edge. It allows you to create a reusable HTML template and enables the introduction of front-end logic into your application with minimal code. Edge provides JavaScript developers with the tools while developing an application to build a component-based layout, write conditionals, use iterations, and create view layers to hold logic. All template files end with the .edge extension and are stored in the resources/views directory.

      The following are the views that your application will need to function properly:

      • Master Layout: With Edge, you can create a page that will contain the CSS, common JavaScript files, jQuery, and common parts of the user interface that will stay the same throughout the application—for example, the navigation bar, logo, header, etc. Once you’ve established the Master Layout page, other views (pages) in your application will inherit it.
      • Index view: This page will use the master layout to inherit common files and will also render contents for the homepage of the application.
      • Login page: This page will also use the master layout and render the form with the input fields for both username and password for users to log in.
      • Register page: Here, users will see a form to register and have their details persisted into the database.
      • Create quote page: Users will use this page to create an inspirational quote.
      • Edit quote page: Users will use this page to edit a quote.
      • View quote page: Users will use this page to view a particular quote.

      To begin, use the adonis command to create the master layout page by running the following command:

      • adonis make:view layouts/master

      You’ll see output similar to the following:

      Output

      ✔ create resources/views/layouts/master.edge

      This command will automatically create a master.edge file in your resources/views/layouts folder. Open the new file:

      • nano resources/views/layouts/master.edge

      Add the following code in it:

      /resources/views/layouts/master.edge

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <title>adonis-quotes-app</title>
          {{ style('https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css') }}
          {{ style('style') }}
          {{ script('https://code.jquery.com/jquery-3.3.1.slim.min.js') }}
      </head>
      <body>
          <div class="container-fliud">
              @include('navbar')
              @!section('content')
          </div>
          {{ script('https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js') }}
      
      </body>
      </html>
      

      In this file, you include the CDN files for Bootstrap CSS, Bootstrap JavaScript, and jQuery. You add a global CSS file name of style.css and within the div you include a partial file named navbar. To reuse fragments of HTML code that you require across multiple pages in your application, like nav or footer, you can incorporate partials. These are smaller files containing the repeated code making it quicker to update code for these elements in one place rather than at every instance it occurs. The navbar contains markup for a Login and Register buttons, a logo, and a home link.

      With this in place, all the subsequent pages that will be created for this application can extend the master layout and have the navbar rendered without the need to write the content all over again. You’ll create this navbar file later in the tutorial.

      Finally, you define a section tag @!section() to include content from other pages and have them rendered by the master layout. For this to work as expected, all the new pages that will extend the master layout must also define a section tag with the same name (i.e., @section('content')).

      Save and exit the file once you’re finished editing it.

      Next, you will use the adonis command to create the navigation bar:

      You’ll see output similar to:

      Output

      ✔ create resources/views/navbar.edge

      Open the newly created file:

      • nano resources/views/navbar.edge

      Then add the following code to it:

      /resources/views/navbar.edge

      <nav class="navbar navbar-expand-lg navbar-dark text-white">
          <a class="navbar-brand" >LOGO</a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
          </button>
      
          <div class="collapse navbar-collapse" id="navbarNav">
              <ul class="navbar-nav">
                  <li class="nav-item active ">
                      <a class="btn text-white" href="/">Home</a>
                  </li>
              </ul>
          </div>
          <div class="navbar-right" id="navbarNav">
              @loggedIn
                  <ul class="navbar-nav">
                          <li>
                              <div class="text-right">
                                   <a href="{{route('create.quote')}}" class="btn btn-outline-primary">Create Quote</a>
                              </div>
                          </li>
      
                      <li class="nav-item dropdown">
                          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                             {{ auth.user.username}}
                          </a>
                          <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
                              <form method="POST" action="{{route('logout')}}">
                                  {{ csrfField() }}
                                    <button  type="submit" class="dropdown-item" href="">logout</button>
                              </form>
                          </div>
                      </li>
                  </ul>
              @else
                  <ul class="navbar-nav">
                      <li class="nav-item active pr-2">
                          <a href="{{route('login.create')}}" class="btn btn-outline-danger">
                            login
                          </a>
                      </li>
                      <li class="nav-item active pr-2">
                          <a href="{{route('register.create')}}" class="btn btn-outline-primary">
                              Register
                          </a>
                      </li>
                  </ul>
              @endloggedIn
          </div>
      </nav>
      

      In addition to defining the links to the homepage and a button to register and login, you add a @loggedIn tag. With this in place you can write a conditional statement around the authenticated user and display appropriate contents where necessary. For an authenticated user, the application will display their username and a button to create a new quote. If a user is not logged in, your application will display a button to either log in or register. This page will be included as a partial on every page as it was earlier in the master layout for this application.

      Save and exit the file.

      Now, you’ll create the index page that you’ll use for the application’s homepage. It will render and display the list of all inspirational quotes that users write:

      You will see an output similar to the following:

      Output

      ✔ create resources/views/index.edge

      The file created here will be located in resources/views/index.edge. Open the file:

      • nano resources/views/index.edge

      Then add the following code:

      /resources/views/index.edge

      @layout('layouts/master')
      @section('content')
      
      <div class="container">
          <div class="text-center">
              @if(flashMessage('successmessage'))
                  <span class="alert alert-success p-1">{{ flashMessage('successmessage') }}</span>
              @endif
          </div>
          <div class="row">
              @each(quote in quotes)
                  <div class="col-md-4 mb-4 quote-wrapper">
                      <a href="http://www.digitalocean.com/view-quote/{{quote.id}}" class="w-100">
                          <div class="card shadow-lg bg-dark text-white">
                              <div class="card-body">
                                  <blockquote class="blockquote mb-0">
                                      <p>{{quote.body}}</p>
                                      <footer class="blockquote-footer">
                                          <cite title="Source Title"> {{quote.username}}</cite>
                                      </footer>
                                  </blockquote>
                                  @if(auth.user.id == quote.user_id)
                                    <div>
                                      <a  href="http://www.digitalocean.com/edit-quote/{{quote.id}}" class="btn btn-primary">edit</a>
                                      <a href="http://www.digitalocean.com/delete-quote/{{quote.id}}" class="btn btn-danger">delete</a>
                                    </div>
                                  @endif
                              </div>
                          </div>
                      </a>
                  </div>
              @else
               <div class="col-md-12 empty-quote text-center">
                      <p>No inspirational quote has been created</p>
               </div>
              @endeach
          </div>
      </div>
      @endsection
      

      Here, you indicate that this view will use the master layout by extending it. This page can now have access to all the libraries, stylesheets, and the navbar included in the master layout. Next, you iterate over an array of quotes using the built-in @each tag. The quotes array will be passed to this view from the QuoteController that you’ll create later in this tutorial. If there are no quotes, an appropriate message will be displayed.

      Save and exit this file.

      Now, to create the login page, run the following command from the terminal:

      • adonis make:view auth/login

      You will see an output similar to:

      Output

      ✔ create resources/views/auth/login.edge

      This will automatically create an auth folder within resources/views and also create a login.edge file within it. Open the login.edge file:

      • nano resources/views/auth/login.edge

      Add the following content:

      /resources/views/auth/login.edge

      @layout('layouts/master')
      @section('content')
        <div class="container">
          <div class="row">
            <div class="col-md-4 shadow bg-white mt-5 rounded offset-md-4">
              <form method="POST" action="{{route('login.store')}}">
                {{ csrfField() }}
                  <div>
                    @if(flashMessage('successmessage'))
                      <span class="alert alert-success p-1">{{ flashMessage('successmessage') }}</span>
                    @endif
                  </div>
                  <div class="form-group">
                    <label for="email">Email address</label>
                    <input type="email" class="form-control" id="email" name="email" value="{{old('email','')}}"  placeholder="Enter email">
                    {{ elIf('<span class=text-danger>$self</span>', getErrorFor('email'), hasErrorFor('email')) }}
                  </div>
                  <div class="form-group">
                    <label for="pasword">Password</label>
                    <input type="password" class="form-control" id="password" name="password" value="{{old('password','')}}" placeholder="Password">
                    {{ elIf('<span class=text-danger>$self</span>', getErrorFor('password'), hasErrorFor('password')) }}
                  </div>
      
                  <div class="text-center">
                    <button type="submit" class="btn btn-primary">Submit</button>
                  </div>
              </form>
            </div>
          </div>
        </div>
      @endsection
      

      This file holds a form that contains input elements that you’ll use to collect the username and password of a registered user before they can successfully get authenticated and start creating quotes. Another important element to note on this page is the {{ csrfField() }}. It is a global variable that AdonisJs will use to pass the CSRF access token when sending a POST, PUT, and DELETE request from your application.

      This was put in place to protect your application from Cross-Site Request Forgery (CSRF) attacks. It works by generating a unique CSRF secret for each user visiting your website and once your users send an HTTP request from the frontend, a corresponding token is generated for this secret and passed along with the request. This will allow the middleware created for this request within AdonisJs to verify that both the token and CSRF secret is valid and belong to the currently authenticated user.

      Save and exit the file once you’re finished.

      Next, you will create the register page with this command:

      • adonis make:view auth/register

      You will see output similar to this:

      Output

      ✔ create resources/views/auth/register.edge

      Locate and open the newly created file in resources/views/auth/register.edge:

      • nano resources/views/auth/register.edge

      Add the following code:

      resources/views/auth/register.edge

      @layout('layouts/master')
      @section('content')
        <div class="container ">
          <div class="row">
              <div class="col-md-4  bg-white p-3 mt-5 shadow no-border rounded offset-md-4">
                <form method="POST" action="{{route('register.store')}}">
                  {{ csrfField() }}
                    <div class="form-group">
                      <label for="name">Fullname</label>
                      <input type="text" class="form-control" id="name" name="name"  value="{{old('name','')}}" placeholder="Enter Fullname">
                      {{ elIf('<span class=text-danger>$self</span>', getErrorFor('name'), hasErrorFor('name')) }}
                    </div>
                    <div class="form-group">
                      <label for="email">Email address</label>
                      <input type="email" class="form-control" id="email"  name="email" value="{{old('email','')}}" placeholder="Enter email">
                      {{ elIf('<span class=text-danger>$self</span>', getErrorFor('email'), hasErrorFor('email')) }}
                    </div>
                    <div class="form-group">
                      <label for="pasword">Password</label>
                      <input type="password" class="form-control" id="password" name="password" placeholder="Password">
                      {{ elIf('<span class=text-danger>$self</span>', getErrorFor('password'), hasErrorFor('password')) }}
                    </div>
                    <div class="text-center">
                        <button type="submit" class="btn btn-primary">Submit</button>
                    </div>
                </form>
              </div>
          </div>
        </div>
      @endsection
      

      Similarly to what you have on the login page, this file contains an HTML form with input fields to collect the name, email, and password of a user during the registration process. Also included is the {{ csrfField() }} as it is required for each post request for an AdonisJs application.

      Save and exit the file.

      Now, you will generate a new file to create an inspirational quote by running the following command from the terminal:

      • adonis make:view quotes/create-quote

      You will see output like:

      Output

      ✔ create resources/views/quotes/create-quote.edge

      Open resources/views/quotes/create-quote.edge:

      • nano resources/views/quotes/create-quote.edge

      And add the following content to it:

      /resources/views/quotes/create-quote.edge

      @layout('layouts/master')
      @section('content')
      <div class="container">
          <div class="row">
              <div class="col-md-3"></div>
              <div class="col-md-6 shadow bg-white mt-5 rounded p-3">
                  <div class="float-right">
                      <a href="/" class="btn btn-outline-dark ">back</a>
                  </div>
                      <br>
      
                  <div class="clear-fix"></div>
                      <form method="POST" action="{{route('store.quote')}}">
                          {{ csrfField() }}
                          <div class="form-group">
                              <label for="quote">Create Quote</label>
                              <textarea type="text" rows="5"  name='body' id="body" class="form-control" id="quote" placeholder="Write an inspirational quote"></textarea>
                          </div>
      
                          <div class="text-center">
                              <button type="submit" class="btn btn-primary">Submit</button>
                          </div>
                      </form>
                  </div>
              </div>
              <div class="col-md-3"></div>
          </div>
      </div>
      @endsection
      

      This page extends the master layout and contains an HTML form with a text area element that allows a user to input text over multiple rows before being posted and handled by the appropriate route.

      Save and exit the file once you’re finished.

      Next, you will create a page for editing a particular quote. Run the following command from the terminal:

      • adonis make:view quotes/edit-quote

      You will see the following output:

      Output

      ✔ create resources/views/quotes/edit-quote.edge

      Open the file with:

      • nano resources/views/quotes/edit-quote.edge

      Add the following content to resources/views/quotes/edit-quote:

      /resources/views/quotes/edit-quote.edge

      @layout('layouts/master')
      @section('content')
      <div class="container">
          <div class="row">
              <div class="col-md-6 shadow bg-white rounded p-3 offset-md-3">
                  <div class="float-right">
                      <a href="/" class="btn btn-outline-dark ">back</a>
                  </div>
                  <br>
      
                  <div class="clear-fix"></div>
                  <form method="POST" action="/update-quote/{{quote.id}}">
                      {{ csrfField() }}
                      <div class="form-group">
                          <label for="pasword">Edit Quote</label>
                          <textarea type="text" rows="5"  name='body' id="body" class="form-control" id="quote" placeholder="write the inspirational quote">{{quote.body}}</textarea>
                      </div>
                      <div class="text-center">
                          <button type="submit" class="btn btn-primary">Update</button>
                      </div>
      
                  </form>
              </div>
          </div>
      </div>
      @endsection
      

      This page holds similar content as the create-quote.edge file—the difference is that it contains the details of a particular quote that needs to be edited, <form method="POST" action="/update-quote/{{quote.id}}">.

      Save and exit the file.

      Finally, generate a page to view a single inspirational quote:

      • adonis make:view quotes/quote

      You will see an output similar to this:

      Output

      ✔ create resources/views/quotes/quote.edge

      Open the file with:

      • nano resources/views/quotes/quote.edge

      Add the following code:

      /resources/views/quotes/quote.edge

      @layout('layouts/master')
      @section('content')
      <div class="container">
          <div class="row">
              <div class="col-md-6 offset-md-3">
                  <div class="card shadow-lg bg-dark text-white">
                      <div class="card-body">
                          <div class="float-right">
                              <a href="/" class="btn btn-outline-primary ">back</a>
                          </div>
                              <br>
                          <div class="clear-fix"></div>
                          <blockquote class="blockquote mb-0">
                              <p>{{quote.body}}</p>
                              <footer class="blockquote-footer">
                                  <cite title="Source Title">{{quote.username}}</cite>
                              </footer>
                          </blockquote>
                      </div>
                  </div>
              </div>
          </div>
      </div>
      @endsection
      

      This page renders the details of a particular quote, that includes the body of the quote, quote.body, and the author who created it, quote.username.

      Once you’re finished with the file, save and exit.

      You have created all the required pages for your application by using the Edge templating engine. Next, you’ll configure and create a connection to your application’s database.

      Step 3 — Creating a Database Schema

      If you serve your application now it will throw an error as you are yet to connect the application to a database. In this section, you will set up a connection to the database and then use the adonis command to generate a migration file that will be used to create the tables for it.

      AdonisJs ships with an ORM named Lucid ORM, which provides active record implementation for working with your database. It takes away the hassle of writing SQL queries that retrieve data from the database in realtime. This is especially helpful when working on a complex application that requires a lot of queries. As an example, retrieving all the quotes from your application can be achieved by writing this:

      const quotes = await Quote.all()
      

      To proceed with the appropriate configuration for your application database, ensure that you are still within the root directory of your application and create a .env file:

      Open the newly created file and add the following content:

      .env

      HOST=127.0.0.1
      PORT=3333
      NODE_ENV=development
      APP_URL=http://${HOST}:${PORT}
      CACHE_VIEWS=false
      APP_KEY=bTVOEgUvmTCfkvgrK8gEBC3Qxt1xSYr0
      DB_CONNECTION=mysql
      DB_HOST=127.0.0.1
      DB_PORT=3306
      DB_USER=sammy
      DB_PASSWORD=password
      DB_DATABASE=adonis
      SESSION_DRIVER=cookie
      HASH_DRIVER=bcrypt
      

      By default the database connection for an AdonisJs application is SQLite, which you will update to MySQL here. You also specify the PORT for the application, the application environment, and your database credentials. Ensure that you replace the DB_USER, DB_PASSWORD, and DB_DATABASE placeholder with your credentials.

      Next, you will create the model and a migration file for Quote using the Adonis CLI. To accomplish that, run the following command:

      • adonis make:model Quote --migration

      You’ll see output similar to the following:

      Output

      ✔ create app/Models/Quote.js ✔ create database/migrations/1568209992854_quote_schema.js

      This command will create a model for Quote in the app/Models folder and a schema file in the database/migrations folder. The newly created schema file will be prefixed with the current timestamp. Open the schema file with:

      • nano database/migrations/1568209992854_quote_schema.js

      Update its content with the following code:

      database/migrations/…quote_schema.js

      'use strict'
      /** @type {import('@adonisjs/lucid/src/Schema')} */
      const Schema = use('Schema')
      class QuoteSchema extends Schema {
        up () {
          this.create('quotes', (table) => {
            table.increments()
            table.integer('user_id').notNullable()
            table.string('username', 80).notNullable()
            table.string('body').notNullable()
            table.timestamps()
          })
        }
        down () {
          this.drop('quotes')
        }
      }
      module.exports = QuoteSchema
      

      A schema file in AdonisJs requires two different methods, which are:

      • up: Used to create a new table or alter an existing one.
      • down: Used to revert the changes applied in the up method.

      In addition to the timestamps() and increments() fields, you update the content of the schema file with the field attributes user_id, username, and the body of a quote that will be created. The user_id and username fields reference the details of the user who create a particular quote. This defines a one to many relationship and means a user can own an infinite number of quotes while a single quote can only belong to a user.

      Save and exit the file.

      AdonisJs comes installed with a User model and its migration file by default, which requires only a small modification to establish the relationship between the User and Quote model.

      Open the User model in app/Models/User.js:

      Add this method immediately after the tokens() method:

      app/Models/User.js

      ...
      class User extends Model {
        ...
        tokens () {
          return this.hasMany('App/Models/Token')
        }
      
        quote () {
          return this.hasMany('App/Models/Quote')
        }
      }
      
      module.exports = User
      

      This will establish a one to many relationship with the Quote table using user_id as the foreign key.

      Save and exit the file.

      To wrap up this section, use the following command to run migrations, which will execute the up() method of all migration files:

      You will see output similar to the following:

      Output

      migrate: 1503248427885_user.js migrate: 1503248427886_token.js migrate: 1568209992854_quote_schema.js Database migrated successfully in 3.42 s

      You’ve configured and secured a connection with your database. You also created a Quote model and its corresponding schema file and created a one to many relationship between a User and Quote. Next, you’ll generate the routes and create controllers to handle HTTP requests and the business logic to create, edit, and delete an inspirational quote.

      Step 4 — Creating Controllers and Setting Up Routes

      In this section, you will start by creating controllers to handle all the logic for the application and later attach these controllers to a specific route for it to be accessed by users via a URL.

      To start, you’ll use the Adonis CLI to create a new HTTP request controller to handle all authentication processes for your application by running the following command:

      • adonis make:controller Auth --type http

      This command will create an AuthController.js file and save it within app/Controllers/Http folder. You use the flag --type to indicate that you want this controller to be an HTTP controller.

      You will see an output similar to the following:

      Output

      ✔ create app/Controllers/Http/AuthController.js

      Next, open the newly created controller file:

      • nano app/Controllers/Http/AuthController.js

      Update it with the following content:

      app/Controllers/Http/AuthController.js

      'use strict'
      const User = use('App/Models/User')
      class AuthController {
      
          loginView({ view }) {
              return view.render('auth.login')
          }
          registrationView({ view }) {
              return view.render('auth.register')
          }
      
          async postLogin({ request, auth, response}) {
              await auth.attempt(request.input('email'), request.input('password'))
              return response.route('index')
          }
      
          async postRegister({ request, session, response }) {
              const user = await User.create({
                  username: request.input('name'),
                  email: request.input('email'),
                  password: request.input('password')
              })
              session.flash({ successmessage: 'User have been created successfully'})
              return response.route('login.create');
          }
      
          async logout ({ auth, response }) {
              await auth.logout()
              return response.route('/')
          }
      }
      module.exports = AuthController
      

      In this file, you import the User model and then create two methods named loginView() and registerView() to render the login and register pages respectively. Finally, you create the following asynchronous methods:

      • postLogin(): This method will obtain the value of the email and password posted through the help of the inbuilt request method in AdonisJs and then validate this user against the details in the database. If such a user exists in the database and has inputted the correct credential, they will be redirected back to the homepage and authenticated before they can create a new quote. Otherwise, a message indicating the wrong credentials will be displayed.
      • postRegister(): This will receive the value of the username, email, and password for a user to create an account for such user in the database. A message indicating that such user has been created successfully will be passed to the session and the user will be redirected to the login page to get authenticated and start creating a quote.
      • logout(): This method will handle the logout functionality and redirect the user back to the homepage.

      Save and exit the file.

      Now that you have set up the controller to register and authenticate users, you will proceed by creating an HTTP request controller to manage all operations regarding quotes.

      Back in the terminal, run the following command to create the QuoteController:

      • adonis make:controller Quote --type http --resource

      Using the --resource flag will create a controller with predefined resourceful methods to do CRUD (Create, Read, Update, and Delete) operations.

      You will see:

      Output

      ✔ create app/Controllers/Http/QuoteController.js

      Locate this file within app/Controllers/Http/QuoteController.js:

      • nano app/Controllers/Http/QuoteController.js

      Update it with the following content:

      app/Controllers/Http/QuoteController.js

      'use strict'
      const Quote = use('App/Models/Quote')
      
      class QuoteController {
      
        async index ({ view }) {
          const quote = await Quote.all()
          return view.render('index', {
            quotes: quote.toJSON()
          })
        }
      
        async create ({ view }) {
          return view.render('quotes.create-quote')
        }
      
        async store ({ request,auth,session, response }) {
          const quote = await Quote.create({
            user_id: auth.user.id,
            username: auth.user.username,
            body: request.input('body')
          })
          session.flash({ 'successmessage': 'Quote has been created'})
          return response.redirect('/')
        }
      
        async show ({ params, view }) {
          const quote = await Quote.find(params.id)
          return view.render('quotes.view-quote', {
            quote: quote.toJSON()
          })
        }
      
        async edit ({ params, view }) {
          const quote = await Quote.find(params.id)
          return view.render('quotes.edit-quote', {
            quote: quote.toJSON()
          })
        }
      
        async update ({ params, request, response, session }) {
          const quote = await Quote.find(params.id)
          quote.body = request.input('body')
          await quote.save()
          session.flash({'successmessage': 'Quote has been updated'})
          return response.redirect('/')
        }
      
        async destroy ({ params, response, session }) {
          const quote = await Quote.find(params.id)
          await quote.delete()
          session.flash({'successmessage': 'Quote has been deleted'})
          return response.redirect('/')
        }
      }
      module.exports = QuoteController
      

      In this controller, you imported the Quote model and updated the following methods that were automatically created by using AdonisJs CLI:

      • index(): to fetch all quotes from the database and render it on the homepage of the application.
      • create(): to render a page for creating quotes.
      • store(): to persist a newly created quote into the database and return an appropriate response.
      • show(): to obtain the id of a particular quote, retrieve it from the database, and render it on the edit quote page.
      • edit(): to obtain the detail of a particular quote from the database and render it for editing.
      • update(): to process any update to a quote and redirect the user back to the homepage.
      • destroy(): to delete a particular quote and remove it entirely from the database.

      Save and exit the file.

      After creating all the necessary controllers for this application, you can now set up the routes so that users can easily interact with your application. To begin, navigate to start/routes.js file

      Replace its content with the following:

      start/routes.js

      'use strict'
      const Route = use('Route')
      
      Route.get('/','QuoteController.index').as('index')
      Route.get('/register','AuthController.registrationView').as('register.create')
      Route.post('/register-store','AuthController.postRegister').as('register.store').validator('Register')
      Route.get('/login','AuthController.loginView').as('login.create')
      Route.post('/login-store','AuthController.postLogin').as('login.store')
      Route.get('/view-quote/:id','QuoteController.show').as('view.quote')
      
      Route.group(() => {
          Route.get('/create-quote','QuoteController.create').as('create.quote')
          Route.post('/store-quote','QuoteController.store').as('store.quote')
          Route.get('/edit-quote/:id','QuoteController.edit').as('edit.quote')
          Route.post('/update-quote/:id','QuoteController.update').as('update.quote')
          Route.get('/delete-quote/:id','QuoteController.destroy').as('delete.quote')
          Route.post('/logout','AuthController.logout').as('logout')
      }).middleware(['auth'])
      

      Here, you define the path for each route in your application, specify the HTTP verbs for each action, and bound the route to a particular method in each controller. You also name each of these routes as they have been referenced within the controllers and views.

      To ensure that only authenticated users can access all the quotes routes, you assign a group middleware named to it. And lastly, you attach a validator method to the register.store route to validate user input.

      Save and exit the file.

      You’ve created your controllers and set up the routes for your application. Next you’ll create the validator method defined in this step.

      Step 5 — Validating User Input

      AdonisJs does not have validators built-in by default. As a result you’ll install and register the validator for your application manually.

      Run the following command to install it:

      Open the following file to register the validator provider:

      Then register the validator provider by appending it to the list of providers as shown following:

      start/app.js

      ...
      const providers = [
         ...
         '@adonisjs/cors/providers/CorsProvider',
         '@adonisjs/shield/providers/ShieldProvider',
         '@adonisjs/session/providers/SessionProvider',
         '@adonisjs/auth/providers/AuthProvider',
         '@adonisjs/validator/providers/ValidatorProvider'
      ]
      

      Now that you have installed and registered the validator provider within your application, create a custom validator to validate user input during registration with the following command:

      • adonis make:validator Register

      This will create a Register.js file in the App/validators directory. Open the file with:

      • nano app/Validators/Register.js

      Add the following code to the file:

      app/Validators/Register.js

      'use strict'
      class Register {
        get rules () {
          return {
            name:'required',
            email:'required|email|unique:users',
            password:'required|min:8'
          }
        }
      
        get messages(){
          return{
            'name.required':'Full name is required',
            'email.required':'email is required',
            'email.unique':'email already exists',
            'password.required':'password is required',
            'password.min':'password should be at least 8 characters'
          }
        }
      }
      module.exports = Register
      

      You define rules for specific fields in your application. If validations fail at any time, the validator automatically sets the error as a flash message and the user will be redirected back to the form.

      Save and exit the file once you’re finished editing.

      Finally, to add styling for your application, open the following file:

      Replace its contents with the following:

      /public/style.css

      @import url('https://fonts.googleapis.com/css?family=Montserrat:300');
      
      html, body {
        height: 100%;
        width: 100%;
      }
      
      body {
        font-family: 'Montserrat', sans-serif;
        font-weight: 300;
        background-image: url("/splash.png");
        background-color: #220052;
      }
      
      * {
        margin: 0;
        padding: 0;
      }
      
      a {
        color: inherit;
        text-decoration: underline;
      }
      
      p {
        margin: 0.83rem 0;
      }
      
      .quote-wrapper {
        margin-top: 20px;
      }
      
      .quote-wrapper a {
        text-decoration: none;
      }
      
      .quote-wrapper a:hover {
        color: #ffffff;
      }
      
      .empty-quote {
        color: #ffffff;
      }
      
      form {
        padding: 20px;
      }
      

      In this file you update the CSS styling of your application in the style.css file.

      You’ve installed and registered a validator provider for the purpose of checking users’ input during the registration process. You also updated the content of your stylesheet to add more styling to the application. In the final step you’ll test your application.

      Step 6 — Serving the Application

      In this step, you’ll serve your application and create a user and password to test the authentication. You’ll also add a quote to your app and view this on the homepage.

      To test your application, start the development server with the following command from the root directory of your application:

      This will start the application on the port defined inside the root .env file, which is 3333. Navigate to http://localhost:3333 from your browser.

      Quote app homepage

      The homepage is empty at the moment as you have not created any quotes. Click on the Register button.

      Registration page

      Enter your details and click on the Submit button to complete the registration process. You will be redirected to the login page. Enter your email address and password for authentication.

      Login page

      Once you’re authenticated, click on the Create Quote button.

      Create quote page

      Enter a quote and navigate to the View all page to see your quote.

      View all quotes page

      You’ve tested your application by creating and authenticating a user and then writing a quote.

      Conclusion

      In this tutorial you’ve built a web application with AdonisJs. You set up the application using the AdonisJs CLI and leveraged the CLI for creating other relevant files such as controllers, models, and views.

      You can build web applications with this framework irrespective of their size and complexity. Feel free to download the source code for this project here on GitHub. To explore further features, you can also visit the official documentation.

      If you would like to explore some of our other JavaScript framework tutorials, check out the following:



      Source link

      How to Set Up a Scalable Laravel 6 Application using Managed Databases and Object Storage


      Introduction

      When scaling web applications horizontally, the first difficulties you’ll typically face are dealing with file storage and data persistence. This is mainly due to the fact that it is hard to maintain consistency of variable data between multiple application nodes; appropriate strategies must be in place to make sure data created in one node is immediately available to other nodes in a cluster.

      A practical way of solving the consistency problem is by using managed databases and object storage systems. The first will outsource data persistence to a managed database, and the latter will provide a remote storage service where you can keep static files and variable content such as images uploaded by users. Each node can then connect to these services at the application level.

      The following diagram demonstrates how such a setup can be used for horizontal scalability in the context of PHP applications:

      Laravel at scale diagram

      In this guide, we will update an existing Laravel 6 application to prepare it for horizontal scalability by connecting it to a managed MySQL database and setting up an S3-compatible object store to save user-generated files. By the end, you will have a travel list application running on an Nginx + PHP-FPM web server:

      Travellist v1.0

      Note: this guide uses DigitalOcean Managed MySQL and Spaces to demonstrate a scalable application setup using managed databases and object storage. The instructions contained here should work in a similar way for other service providers.

      Prerequisites

      To begin this tutorial, you will first need the following prerequisites:

      • Access to an Ubuntu 18.04 server as a non-root user with sudo privileges, and an active firewall installed on your server. To set these up, please refer to our Initial Server Setup Guide for Ubuntu 18.04.
      • Nginx and PHP-FPM installed and configured on your server, as explained in steps 1 and 3 of How to Install LEMP on Ubuntu 18.04. You should skip the step where MySQL is installed.
      • Composer installed on your server, as explained in steps 1 and 2 of How to Install and Use Composer on Ubuntu 18.04.
      • Admin credentials to a managed MySQL 8 database. For this guide, we’ll be using a DigitalOcean Managed MySQL cluster, but the instructions here should work similarly for other managed database services.
      • A set of API keys with read and write permissions to an S3-compatible object storage service. In this guide, we’ll use DigitalOcean Spaces, but you are free to use a provider of your choice.
      • The s3cmd tool installed and configured to connect to your object storage drive. For instructions on how to set this up for DigitalOcean Spaces, please refer to our product documentation.

      Step 1 — Installing the MySQL 8 Client

      The default Ubuntu apt repositories come with the MySQL 5 client, which is not compatible with the MySQL 8 server we’ll be using in this guide. To install the compatible MySQL client, we’ll need to use the MySQL APT Repository provided by Oracle.

      Begin by navigating to the MySQL APT Repository page in your web browser. Find the Download button in the lower-right corner and click through to the next page. This page will prompt you to log in or sign up for an Oracle web account. You can skip that and instead look for the link that says No thanks, just start my download. Copy the link address and go back to your terminal window.

      This link should point to a .deb package that will set up the MySQL APT Repository in your server. After installing it, you’ll be able to use apt to install the latest releases of MySQL. We’ll use curl to download this file into a temporary location.

      Go to your server’s tmp folder:

      Now download the package with curl and using the URL you copied from the MySQL APT Repository page:

      • curl -OL https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb

      After the download is finished, you can use dpkg to install the package:

      • sudo dpkg -i mysql-apt-config_0.8.13-1_all.deb

      You will be presented with a screen where you can choose which MySQL version you’d like to select as default, as well as which MySQL components you’re interested in:

      MySQL APT Repository Install

      You don’t need to change anything here, because the default options will install the repositories we need. Select “Ok” and the configuration will be finished.

      Next, you’ll need to update your apt cache with:

      Now we can finally install the MySQL 8 client with:

      • sudo apt install mysql-client

      Once that command finishes, check the software version number to ensure that you have the latest release:

      You’ll see output like this:

      Output

      mysql Ver 8.0.18 for Linux on x86_64 (MySQL Community Server - GPL)

      In the next step, we’ll use the MySQL client to connect to your managed MySQL server and prepare the database for the application.

      Step 2 — Creating a new MySQL User and Database

      At the time of this writing, the native MySQL PHP library mysqlnd doesn’t support caching_sha2_authentication, the default authentication method for MySQL 8. We’ll need to create a new user with the mysql_native_password authentication method in order to be able to connect our Laravel application to the MySQL 8 server. We’ll also create a dedicated database for our demo application.

      To get started, log into your server using an admin account. Replace the highlighted values with your own MySQL user, host, and port:

      • mysql -u MYSQL_USER -p -h MYSQL_HOST -P MYSQL_PORT

      When prompted, provide the admin user’s password. After logging in, you will have access to the MySQL 8 server command line interface.

      First, we’ll create a new database for the application. Run the following command to create a new database named travellist:

      • CREATE DATABASE travellist;

      Next, we’ll create a new user and set a password, using mysql_native_password as default authentication method for this user. You are encouraged to replace the highlighted values with values of your own, and to use a strong password:

      • CREATE USER "http://www.digitalocean.com/travellist-user'@'%' IDENTIFIED WITH mysql_native_password BY "http://www.digitalocean.com/MYSQL_PASSWORD';

      Now we need to give this user permission over our application database:

      • GRANT ALL ON travellist.* TO "http://www.digitalocean.com/travellist-user'@'%';

      You can now exit the MySQL prompt with:

      You now have a dedicated database and a compatible user to connect from your Laravel application. In the next step, we’ll get the application code and set up configuration details, so your app can connect to your managed MySQL database.

      In this guide, we’ll use Laravel Migrations and database seeds to set up our application tables. If you need to migrate an existing local database to a DigitalOcean Managed MySQL database, please refer to our documentation on How to Import MySQL Databases into DigitalOcean Managed Databases.

      Step 3 — Setting Up the Demo Application

      To get started, we’ll fetch the demo Laravel application from its Github repository. Feel free to inspect the contents of the application before running the next commands.

      The demo application is a travel bucket list app that was initially developed in our guide on How to Install and Configure Laravel with LEMP on Ubuntu 18.04. The updated app now contains visual improvements including travel photos that can be uploaded by a visitor, and a world map. It also introduces a database migration script and database seeds to create the application tables and populate them with sample data, using artisan commands.

      To obtain the application code that is compatible with this tutorial, we’ll download the 1.1 release from the project’s repository on Github. We’ll save the downloaded zip file as travellist.zip inside our home directory:

      • cd ~
      • curl -L https://github.com/do-community/travellist-laravel-demo/archive/1.1.zip -o travellist.zip

      Now, unzip the contents of the application and rename its directory with:

      • unzip travellist.zip
      • mv travellist-laravel-demo-1.1 travellist

      Navigate to the travellist directory:

      Before going ahead, we’ll need to install a few PHP modules that are required by the Laravel framework, namely: php-xml, php-mbstring, php-xml and php-bcmath. To install these packages, run:

      • sudo apt install unzip php-xml php-mbstring php-xml php-bcmath

      To install the application dependencies, run:

      You will see output similar to this:

      Output

      Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 80 installs, 0 updates, 0 removals - Installing doctrine/inflector (v1.3.0): Downloading (100%) - Installing doctrine/lexer (1.1.0): Downloading (100%) - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%) - Installing erusev/parsedown (1.7.3): Downloading (100%) ... Generating optimized autoload files > IlluminateFoundationComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: beyondcode/laravel-dump-server Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.

      The application dependencies are now installed. Next, we’ll configure the application to connect to the managed MySQL Database.

      Creating the .env configuration file and setting the App Key

      We’ll now create a .env file containing variables that will be used to configure the Laravel application in a per-environment basis. The application includes an example file that we can copy and then modify its values to reflect our environment settings.

      Copy the .env.example file to a new file named .env:

      Now we need to set the application key. This key is used to encrypt session data, and should be set to a unique 32 characters-long string. We can generate this key automatically with the artisan tool:

      Let’s edit the environment configuration file to set up the database details. Open the .env file using your command line editor of choice. Here, we will be using nano:

      Look for the database credentials section. The following variables need your attention:

      DB_HOST: your managed MySQL server host.
      DB_PORT: your managed MySQL server port.
      DB_DATABASE: the name of the application database we created in Step 2.
      DB_USERNAME: the database user we created in Step 2.
      DB_PASSWORD: the password for the database user we defined in Step 2.

      Update the highlighted values with your own managed MySQL info and credentials:

      ...
      DB_CONNECTION=mysql
      DB_HOST=MANAGED_MYSQL_HOST
      DB_PORT=MANAGED_MYSQL_PORT
      DB_DATABASE=MANAGED_MYSQL_DB
      DB_USERNAME=MANAGED_MYSQL_USER
      DB_PASSWORD=MANAGED_MYSQL_PASSWORD
      ...
      

      Save and close the file by typing CTRL+X then Y and ENTER when you’re done editing.

      Now that the application is configured to connect to the MySQL database, we can use Laravel’s command line tool artisan to create the database tables and populate them with sample data.

      Migrating and populating the database

      We’ll now use Laravel Migrations and database seeds to set up the application tables. This will help us determine if our database configuration works as expected.

      To execute the migration script that will create the tables used by the application, run:

      You will see output similar to this:

      Output

      Migration table created successfully. Migrating: 2019_09_19_123737_create_places_table Migrated: 2019_09_19_123737_create_places_table (0.26 seconds) Migrating: 2019_10_14_124700_create_photos_table Migrated: 2019_10_14_124700_create_photos_table (0.42 seconds)

      To populate the database with sample data, run:

      You will see output like this:

      Output

      Seeding: PlacesTableSeeder Seeded: PlacesTableSeeder (0.86 seconds) Database seeding completed successfully.

      The application tables are now created and populated with sample data.

      To finish the application setup, we also need to create a symbolic link to the public storage folder that will host the travel photos we’re using in the application. You can do that using the artisan tool:

      Output

      The [public/storage] directory has been linked.

      This will create a symbolic link inside the public directory pointing to storage/app/public, where we’ll save the travel photos. To check that the link was created and where it points to, you can run:

      You’ll see output like this:

      Output

      total 36 drwxrwxr-x 5 sammy sammy 4096 Oct 25 14:59 . drwxrwxr-x 12 sammy sammy 4096 Oct 25 14:58 .. -rw-rw-r-- 1 sammy sammy 593 Oct 25 06:29 .htaccess drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 css -rw-rw-r-- 1 sammy sammy 0 Oct 25 06:29 favicon.ico drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 img -rw-rw-r-- 1 sammy sammy 1823 Oct 25 06:29 index.php drwxrwxr-x 2 sammy sammy 4096 Oct 25 06:29 js -rw-rw-r-- 1 sammy sammy 24 Oct 25 06:29 robots.txt lrwxrwxrwx 1 sammy sammy 41 Oct 25 14:59 storage -> /home/sammy/travellist/storage/app/public -rw-rw-r-- 1 sammy sammy 1194 Oct 25 06:29 web.config

      Running the test server (optional)

      You can use the artisan serve command to quickly verify that everything is set up correctly within the application, before having to configure a full-featured web server like Nginx to serve the application for the long term.

      We’ll use port 8000 to temporarily serve the application for testing. If you have the UFW firewall enabled on your server, you should first allow access to this port with:

      Now, to run the built in PHP server that Laravel exposes through the artisan tool, run:

      • php artisan serve --host=0.0.0.0 --port=8000

      This command will block your terminal until interrupted with a CTRL+C. It will use the built-in PHP web server to serve the application for test purposes on all network interfaces, using port 8000.

      Now go to your browser and access the application using the server’s domain name or IP address on port 8000:

      http://server_domain_or_IP:8000
      

      You will see the following page:

      Travellist v1.0

      If you see this page, it means the application is successfully pulling data about locations and photos from the configured managed database. The image files are still stored in the local disk, but we’ll change this in a following step of this guide.

      When you are finished testing the application, you can stop the serve command by hitting CTRL+C.

      Don’t forget to close port 8000 again if you are running UFW on your server:

      Step 4 — Configuring Nginx to Serve the Application

      Although the built-in PHP web server is very useful for development and testing purposes, it is not intended to be used as a long term solution to serve PHP applications. Using a full featured web server like Nginx is the recommended way of doing that.

      To get started, we’ll move the application folder to /var/www, which is the usual location for web applications running on Nginx. First, use the mv command to move the application folder with all its contents to /var/www/travellist:

      • sudo mv ~/travellist /var/www/travellist

      Now we need to give the web server user write access to the storage and bootstrap/cache folders, where Laravel stores application-generated files. We’ll set these permissions using setfacl, a command line utility that allows for more robust and fine-grained permission settings in files and folders.

      To include read, write and execution (rwx) permissions to the web server user over the required directories, run:

      • sudo setfacl -R -m g:www-data:rwx /var/www/travellist/storage
      • sudo setfacl -R -m g:www-data:rwx /var/www/travellist/bootstrap/cache

      The application files are now in order, but we still need to configure Nginx to serve the content. To do this, we’ll create a new virtual host configuration file at /etc/nginx/sites-available:

      • sudo nano /etc/nginx/sites-available/travellist

      The following configuration file contains the recommended settings for Laravel applications on Nginx:

      /etc/nginx/sites-available/travellist

      server {
          listen 80;
          server_name server_domain_or_IP;
          root /var/www/travellist/public;
      
          add_header X-Frame-Options "SAMEORIGIN";
          add_header X-XSS-Protection "1; mode=block";
          add_header X-Content-Type-Options "nosniff";
      
          index index.html index.htm index.php;
      
          charset utf-8;
      
          location / {
              try_files $uri $uri/ /index.php?$query_string;
          }
      
          location = /favicon.ico { access_log off; log_not_found off; }
          location = /robots.txt  { access_log off; log_not_found off; }
      
          error_page 404 /index.php;
      
          location ~ .php$ {
              fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
              fastcgi_index index.php;
              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
              include fastcgi_params;
          }
      
          location ~ /.(?!well-known).* {
              deny all;
          }
      }
      

      Copy this content to your /etc/nginx/sites-available/travellist file and adjust the highlighted values to align with your own configuration. Save and close the file when you’re done editing.

      To activate the new virtual host configuration file, create a symbolic link to travellist in sites-enabled:

      • sudo ln -s /etc/nginx/sites-available/travellist /etc/nginx/sites-enabled/

      Note: If you have another virtual host file that was previously configured for the same server_name used in the travellist virtual host, you might need to deactivate the old configuration by removing the corresponding symbolic link inside /etc/nginx/sites-enabled/.

      To confirm that the configuration doesn’t contain any syntax errors, you can use:

      You should see output like this:

      Output

      • nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
      • nginx: configuration file /etc/nginx/nginx.conf test is successful

      To apply the changes, reload Nginx with:

      • sudo systemctl reload nginx

      If you reload your browser now, the application images will be broken. That happens because we moved the application directory to a new location inside the server, and for that reason we need to re-create the symbolic link to the application storage folder.

      Remove the old link with:

      • cd /var/www/travellist
      • rm -f public/storage

      Now run once again the artisan command to generate the storage link:

      Now go to your browser and access the application using the server’s domain name or IP address, as defined by the server_name directive in your configuration file:

      http://server_domain_or_IP
      

      Travellist v1.0

      In the next step, we’ll integrate an object storage service into the application. This will replace the current local disk storage used for the travel photos.

      Step 5 — Integrating an S3-Compatible Object Storage into the Application

      We’ll now set up the application to use an S3-compatible object storage service for storing the travel photos exhibited on the index page. Because the application already has a few sample photos stored in the local disk, we’ll also use the s3cmd tool to upload the existing local image files to the remote object storage.

      Setting Up the S3 Driver for Laravel

      Laravel uses league/flysystem, a filesystem abstraction library that enables a Laravel application to use and combine multiple storage solutions, including local disk and cloud services. An additional package is required to use the s3 driver. We can install this package using the composer require command.

      Access the application directory:

      • composer require league/flysystem-aws-s3-v3

      You will see output similar to this:

      Output

      Using version ^1.0 for league/flysystem-aws-s3-v3 ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 8 installs, 0 updates, 0 removals - Installing mtdowling/jmespath.php (2.4.0): Loading from cache - Installing ralouphie/getallheaders (3.0.3): Loading from cache - Installing psr/http-message (1.0.1): Loading from cache - Installing guzzlehttp/psr7 (1.6.1): Loading from cache - Installing guzzlehttp/promises (v1.3.1): Loading from cache - Installing guzzlehttp/guzzle (6.4.1): Downloading (100%) - Installing aws/aws-sdk-php (3.112.28): Downloading (100%) - Installing league/flysystem-aws-s3-v3 (1.0.23): Loading from cache ...

      Now that the required packages are installed, we can update the application to connect to the object storage. First, we’ll open the .env file again to set up configuration details such as keys, bucket name, and region for your object storage service.

      Open the .env file:

      Include the following environment variables, replacing the highlighted values with your object store configuration details:

      /var/www/travellist/.env

      DO_SPACES_KEY=EXAMPLE7UQOTHDTF3GK4
      DO_SPACES_SECRET=exampleb8e1ec97b97bff326955375c5
      DO_SPACES_ENDPOINT=https://ams3.digitaloceanspaces.com
      DO_SPACES_REGION=ams3
      DO_SPACES_BUCKET=sammy-travellist
      

      Save and close the file when you’re done. Now open the config/filesystems.php file:

      • nano config/filesystems.php

      Within this file, we’ll create a new disk entry in the disks array. We’ll name this disk spaces, and we’ll use the environment variables we’ve set in the .env file to configure the new disk. Include the following entry in the disks array:

      config/filesystems.php

      
      'spaces' => [
         'driver' => 's3',
         'key' => env('DO_SPACES_KEY'),
         'secret' => env('DO_SPACES_SECRET'),
         'endpoint' => env('DO_SPACES_ENDPOINT'),
         'region' => env('DO_SPACES_REGION'),
         'bucket' => env('DO_SPACES_BUCKET'),
      ],
      
      

      Still in the same file, locate the cloud entry and change it to set the new spaces disk as default cloud filesystem disk:

      config/filesystems.php

      'cloud' => env('FILESYSTEM_CLOUD', "http://www.digitalocean.com/spaces'),
      

      Save and close the file when you’re done editing. From your controllers, you can now use the Storage::cloud() method as a shortcut to access the default cloud disk. This way, the application stays flexible to use multiple storage solutions, and you can switch between providers on a per-environment basis.

      The application is now configured to use object storage, but we still need to update the code that uploads new photos to the application.

      Let’s first examine the current uploadPhoto route, located in the PhotoController class. Open the file using your text editor:

      • nano app/Http/Controllers/PhotoController.php

      app/Http/Controllers/PhotoController.php

      …
      
      public function uploadPhoto(Request $request)
      {
         $photo = new Photo();
         $place = Place::find($request->input('place'));
      
         if (!$place) {
             //add new place
             $place = new Place();
             $place->name = $request->input('place_name');
             $place->lat = $request->input('place_lat');
             $place->lng = $request->input('place_lng');
         }
      
         $place->visited = 1;
         $place->save();
      
         $photo->place()->associate($place);
         $photo->image = $request->image->store('/', 'public');
         $photo->save();
      
         return redirect()->route('Main');
      }
      
      

      This method accepts a POST request and creates a new photo entry in the photos table. It begins by checking if an existing place was selected in the photo upload form, and if that’s not the case, it will create a new place using the provided information. The place is then set to visited and saved to the database. Following that, an association is created so that the new photo is linked to the designated place. The image file is then stored in the root folder of the public disk. Finally, the photo is saved to the database. The user is then redirected to the main route, which is the index page of the application.

      The highlighted line in this code is what we’re interested in. In that line, the image file is saved to the disk using the store method. The store method is used to save files to any of the disks defined in the filesystem.php configuration file. In this case, it is using the default disk to store uploaded images.

      We will change this behavior so that the image is saved to the object store instead of the local disk. In order to do that, we need to replace the public disk by the spaces disk in the store method call. We also need to make sure the uploaded file’s visibility is set to public instead of private.

      The following code contains the full PhotoController class, including the updated uploadPhoto method:

      app/Http/Controllers/PhotoController.php

      <?php
      
      namespace AppHttpControllers;
      
      use IlluminateHttpRequest;
      use AppPhoto;
      use AppPlace;
      use IlluminateSupportFacadesStorage;
      
      class PhotoController extends Controller
      {
         public function uploadForm()
         {
             $places = Place::all();
      
             return view('upload_photo', [
                 'places' => $places
             ]);
         }
      
         public function uploadPhoto(Request $request)
         {
             $photo = new Photo();
             $place = Place::find($request->input('place'));
      
             if (!$place) {
                 //add new place
                 $place = new Place();
                 $place->name = $request->input('place_name');
                 $place->lat = $request->input('place_lat');
                 $place->lng = $request->input('place_lng');
             }
      
             $place->visited = 1;
             $place->save();
      
             $photo->place()->associate($place);
             $photo->image = $request->image->store('/', 'spaces');
             Storage::setVisibility($photo->image, 'public');
             $photo->save();
      
             return redirect()->route('Main');
         }
      }
      
      
      

      Copy the updated code to your own PhotoController so that it reflects the highlighted changes. Save and close the file when you’re done editing.

      We still need to modify the application’s main view so that it uses the object storage file URL to render the image. Open the travel_list.blade.php template:

      • nano resources/views/travel_list.blade.php

      Now locate the footer section of the page, which currently looks like this:

      resources/views/travel_list.blade.php

      @section('footer')
         <h2>Travel Photos <small>[ <a href="{{ route('Upload.form') }}">Upload Photo</a> ]</small></h2>
         @foreach ($photos as $photo)
             <div class="photo">
                <img src="https://www.digitalocean.com/{{ asset('storage') . '/' . $photo->image }}" />
                 <p>{{ $photo->place->name }}</p>
             </div>
         @endforeach
      
      @endsection
      

      Replace the current image src attribute to use the file URL from the spaces storage disk:

      <img src="https://www.digitalocean.com/{{ Storage::disk('spaces')->url($photo->image) }}" />
      

      If you go to your browser now and reload the application page, it will show only broken images. That happens because the image files for those travel photos are still only in the local disk. We need to upload the existing image files to the object storage, so that the photos already stored in the database can be successfully exhibited in the application page.

      Syncing local images with s3cmd

      The s3cmd tool can be used to sync local files with an S3-compatible object storage service. We’ll run a sync command to upload all files inside storage/app/public/photos to the object storage service.

      Access the public app storage directory:

      • cd /var/www/travellist/storage/app/public

      To have a look at the files already stored in your remote disk, you can use the s3cmd ls command:

      • s3cmd ls s3://your_bucket_name

      Now run the sync command to upload existing files in the public storage folder to the object storage:

      • s3cmd sync ./ s3://your_bucket_name --acl-public --exclude=.gitignore

      This will synchronize the current folder (storage/app/public) with the remote object storage’s root dir. You will get output similar to this:

      Output

      upload: './bermudas.jpg' -> 's3://sammy-travellist/bermudas.jpg' [1 of 3] 2538230 of 2538230 100% in 7s 329.12 kB/s done upload: './grindavik.jpg' -> 's3://sammy-travellist/grindavik.jpg' [2 of 3] 1295260 of 1295260 100% in 5s 230.45 kB/s done upload: './japan.jpg' -> 's3://sammy-travellist/japan.jpg' [3 of 3] 8940470 of 8940470 100% in 24s 363.61 kB/s done Done. Uploaded 12773960 bytes in 37.1 seconds, 336.68 kB/s.

      Now, if you run s3cmd ls again, you will see that three new files were added to the root folder of your object storage bucket:

      • s3cmd ls s3://your_bucket_name

      Output

      2019-10-25 11:49 2538230 s3://sammy-travellist/bermudas.jpg 2019-10-25 11:49 1295260 s3://sammy-travellist/grindavik.jpg 2019-10-25 11:49 8940470 s3://sammy-travellist/japan.jpg

      Go to your browser and reload the application page. All images should be visible now, and if you inspect them using your browser debug tools, you’ll notice that they’re all using URLs from your object storage.

      Testing the Integration

      The demo application is now fully functional, storing files in a remote object storage service, and saving data to a managed MySQL database. We can now upload a few photos to test our setup.

      Access the /upload application route from your browser:

      http://server_domain_or_IP/upload
      

      You will see the following form:

      Travellist  Photo Upload Form

      You can now upload a few photos to test the object storage integration. After choosing an image from your computer, you can select an existing place from the dropdown menu, or you can add a new place by providing its name and geographic coordinates so it can be loaded in the application map.

      Step 6 — Scaling Up a DigitalOcean Managed MySQL Database with Read-Only Nodes (Optional)

      Because read-only operations are typically more frequent than writing operations on database servers, its is a common practice to scale up a database cluster by setting up multiple read-only nodes. This will distribute the load generated by SELECT operations.

      To demonstrate this setup, we’ll first add 2 read-only nodes to our DigitalOcean Managed MySQL cluster. Then, we’ll configure the Laravel application to use these nodes.

      Access the DigitalOcean Cloud Panel and follow these instructions:

      1. Go to Databases and select your MySQL cluster.
      2. Click Actions and choose Add a read-only node from the drop-down menu.
      3. Configure the node options and hit the Create button. Notice that it might take several minutes for the new node to be ready.
      4. Repeat steps 1-4 one more time so that you have 2 read-only nodes.
      5. Note down the hosts of the two nodes as we will need them for our Laravel configuration.

      Once you have your read-only nodes ready, head back to your terminal.

      We’ll now configure our Laravel application to work with multiple database nodes. When we’re finished, queries such as INSERT and UPDATE will be forwarded to your primary cluster node, while all SELECT queries will be redirected to your read-only nodes.

      First, go to the application’s directory on the server and open your .env file using your text editor of choice:

      • cd /var/www/travellist
      • nano .env

      Locate the MySQL database configuration and comment out the DB_HOST line:

      /var/www/travellist/.env

      DB_CONNECTION=mysql
      #DB_HOST=MANAGED_MYSQL_HOST
      DB_PORT=MANAGED_MYSQL_PORT
      DB_DATABASE=MANAGED_MYSQL_DB
      DB_USERNAME=MANAGED_MYSQL_USER
      DB_PASSWORD=MANAGED_MYSQL_PASSWORD
      

      Save and close the file when you’re done. Now open the config/database.php in your text editor:

      Look for the mysql entry inside the connections array. You should include three new items in this configuration array: read, write, and sticky. The read and write entries will set up the cluster nodes, and the sticky option set to true will reuse write connections so that data written to the database is immediately available in the same request cycle. You can set it to false if you don’t want this behavior.

      /var/www/travel_list/config/database.php

      ...
            'mysql' => [
               'read' => [
                 'host' => [
                    "http://www.digitalocean.com/READONLY_NODE1_HOST',
                    "http://www.digitalocean.com/READONLY_NODE2_HOST',
                 ],
               ],
               'write' => [
                 'host' => [
                   "http://www.digitalocean.com/MANAGED_MYSQL_HOST',
                 ],
               ],
             'sticky' => true,
      ...
      

      Save and close the file when you are done editing. To test that everything works as expected, we can create a temporary route inside routes/web.php to pull some data from the database and show details about the connection being used. This way we will be able to see how the requests are being load balanced between the read-only nodes.

      Open the routes/web.php file:

      Include the following route:

      /var/www/travel_list/routes/web.php

      ...
      
      Route::get('/mysql-test', function () {
        $places = AppPlace::all();
        $results = DB::select( DB::raw("SHOW VARIABLES LIKE 'server_id'") );
      
        return "Server ID: " . $results[0]->Value;
      });
      

      Now go to your browser and access the /mysql-test application route:

      http://server_domain_or_IP/mysql-test
      

      You’ll see a page like this:

      mysql node test page

      Reload the page a few times and you will notice that the Server ID value changes, indicating that the requests are being randomly distributed between the two read-only nodes.

      Conclusion

      In this guide, we’ve prepared a Laravel 6 application for a highly available and scalable environment. We’ve outsourced the database system to an external managed MySQL service, and we’ve integrated an S3-compatible object storage service into the application to store files uploaded by users. Finally, we’ve seen how to scale up the application’s database by including additional read-only cluster nodes in the app’s configuration file.

      The updated demo application code containing all modifications made in this guide can be found within the 2.1 tag in the application’s repository on Github.

      From here, you can set up a Load Balancer to distribute load and scale your application among multiple nodes. You can also leverage this setup to create a containerized environment to run your application on Docker.



      Source link