Consuming JSON API using jQuery 3.2.1, Vue.js 2.3.3 in Rails 5.1

In this article we will use jQuery 3.2.1 to consume JSON API implemented using Rails 5.1 API only app. You will learn about Rack CORS middleware and making AJAX call to get data from the server. We will finish by using Vue.js to render a table to display the data from the server.

Setup Rails 5.1 API Server

Create a new Rails 5.1 API only app.

rails new capi --api

Create a resource:

rails g scaffold article title content:text

Add:

gem 'mysql2'

to Gemfile and run bundle.

Without the mysql2 gem you will get the error:

Specified 'mysql2' for database adapter, but the gem is not loaded. Add `gem 'mysql2'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord).
Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf8", "reconnect"=>false, "database"=>"capi_development", "pool"=>5, "username"=>"root", "password"=>nil}

Create and migrate the database:

rails db:create
rails db:migrate

The database.yml for MySQL server in development:

development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: capi_development
  pool: 5
  username: root
  password:

test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

I had imported production data into my development environment for rubyplus app. I wanted to export only articles table. So, in the terminal, I ran:

mysqldump -p ––user=db-user-name db-name articles > articles.sql

Then I imported the data for the articles table into my new project by running:

mysql -u username -p -D capi_development < articles.sql

Make sure you are in the same directory where you exported the data, because articles.sql will contain the data. Run the rails server and go to http://localhost:3000/articles. You can install JSON Viewer Chrome extension to view the formatted JSON.

jQuery API Client

The front-end app will consist of an html file and a CSS file. Create index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello jQuery JSON</title>
    <script src="https://code.jquery.com/jquery-3.2.1.js"></script>
  </head>

  <body>
  </body>

  <script>
    $.getJSON( "http://localhost:3030/articles", function(data) {
      console.log( "JSON Data: " + data);
     });
  </script>
</html>

We are using CDN for the jQuery library. We are making a GET request to the server to retreive list of all articles. We print the data we get back from the server in the Javascript console. Open the index.html file on a browser, inspect to view the browser console, you will see an error message:

XMLHttpRequest cannot load http://localhost:3030/articles. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

Enable CORS in Rails

Uncomment:

gem 'rack-cors'

in Gemfile. Run bundle. Open config/initializers/cors.rb:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

You can see that CORS rack is now part of our Rails app middleware:

$rails middleware
use Rack::Cors
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Capi::Application.routes

Reload the index.html file. It will now work. There will be no error message. You can see the response headers related to CORS in the developer console:


Let's inspect the data. Change the javascript:

JSON.stringify(object)

You can now see the data in the browser console. How do we iterate through this JSON object in jQuery? We can use $.each for looping in jQuery.

<script>
  $.getJSON( "http://localhost:3030/articles", function(data) {
    var articles = JSON.stringify(data);
    $.each(articles, function(k, v){
      console.log("Key: " + k + ", Value: " + v);
    });
    console.log("Article 1: " + articles)
   });
</script>

This gives:

Uncaught TypeError: Cannot use 'in' operator to search for

We have a JSON string, not an object. We can check the data from the server and the size:

<script>
  $.getJSON("http://localhost:3030/articles", function(data) {
    console.log("Article 1: " + data);
    console.log("Article 1: " + data.size);
   });
</script>

Set a breakpoint inside the getJSON method. The data.size is not available, you can see in the console, length is available. You can also see in the console that the objects are in an array. The array size is 10.


Reading the jQuery docs, we find that we can iterate over the array:

$.each( [ "a", "b", "c" ], function( i, l ){
  alert( "Index #" + i + ": " + l );
});

Let's loop through an array:

<script>
  $.getJSON( "http://localhost:3030/articles", function(data) {
    $.each(data, function(i, a){
      console.log("Index: " + i + ", Value: " + a);
    });
    console.log("Article 1: " + data)
   });
</script>

We can list article id and title for all the articles:

<script>
 $.getJSON( "http://localhost:3030/articles", function(data) {
   $.each(data, function(i, article){
     console.log(article['id']);
     console.log(article['title']);
   });
  });
</script>

You will now see the article id and title in the browser console. We can now display the article id and title on the browser using jQuery.

<!DOCTYPE html>
<html>
  <head>
    <title>Hello jQuery JSON</title>
    <script src="https://code.jquery.com/jquery-3.2.1.js"></script>
  </head>

  <body>
    <p>Articles</p>
    <ul id='article_list'></ul>
  </body>

  <script>
    $.getJSON( "http://localhost:3030/articles", function(data) {
      var $ul = $('#article_list')
      $.each(data, function(i, article){
        $ul.append("<li>" + article.id + ": " + article.title)
      });
     });
  </script>
</html>

We create an ul element and append the article id and title inside the loop. We can handle success and failure cases separately:

  <script>
    $.getJSON("http://localhost:3030/articles") 
      .done(function(data) {
        var $ul = $('#article_list')
        $.each(data, function(i, article){
          $ul.append("<li>" + article.id + ": " + article.title)
          console.log(article['id']);
          console.log(article['title']);
        });       
      })   
      .fail(function( jqxhr, textStatus, error ) {
        var err = textStatus + ", " + error;
        console.log( "Request Failed: " + err ); 
     });
  </script>

You can see the done function handles the success case whereas the fail case handles any failures.

Using Vue.js to Render a Table

Using jQuery to generate dynamic html is very ugly and difficult to maintain in the long run. We can use Vue.js to render the html dynamically. The index.html will be:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello jQuery Vue.js </title>
    <link rel="stylesheet" type="text/css" href="static.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
    <script src="https://code.jquery.com/jquery-3.2.1.js"></script>
  </head>

  <body>
    <div id="container">
      <h1>My Blog</h1> 

      <table>
        <thead>
          <tr>
            <th>id</th>
            <th>Title</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="article in articles">
            <td>{{article.id}}</td>
            <td>{{article.title}}</td>
          </tr>
        </tbody>
      </table>  
    </div>
  </body>

  <script type='text/javascript'>
    var v = new Vue({ 
      el: '#container',
      data: {
        articles: []
      },
      mounted: function(){
        $.get('http://localhost:3030/articles', function(data){ 
          v.articles = data;
        })}
    })    
  </script>
</html>

We use jQuery to make the ajax call within the mounted hook. We can only make the AJAX call after Vue.js instance is mounted to the DOM. You can read more about the life cycle events to get a deeper understanding. You can see now the html and javascript are clean. The intermingling of html and javascript is minimized. The data we get from the server is set to the articles collection of the Vue instance.

The static.css is as shown below:

body {
  background-color: #444444;
  font-family: Verdana, Helvetica, Arial;
  font-size: 14px;
}

a img {
  border: none;
}

a {
  color: #0000FF;
}

.clear {
  clear: both;
  height: 0;
  overflow: hidden;
}

#container {
  width: 75%;
  margin: 0 auto;
  background-color: #FFF;
  padding: 20px 40px;
  border: solid 1px black;
  margin-top: 20px;
  position: relative;
}

#flash_notice, #flash_error, #flash_alert {
  padding: 5px 8px;
  margin: 10px 0;
  margin-right: 150px;
}

#flash_notice {
  background-color: #CFC;
  border: solid 1px #6C6;
}

#flash_error, #flash_alert {
  background-color: #FCC;
  border: solid 1px #C66;
}

.field_with_errors {
  display: inline;
}

.error_messages {
  width: 400px;
  border: 2px solid #CF0000;
  padding: 0px;
  padding-bottom: 12px;
  margin-bottom: 20px;
  background-color: #f0f0f0;
  font-size: 12px;
}

.error_messages h2 {
  text-align: left;
  font-weight: bold;
  padding: 5px 10px;
  font-size: 12px;
  margin: 0;
  background-color: #c00;
  color: #fff;
}

.error_messages p {
  margin: 8px 10px;
}

form .field, form .actions {
  margin: 12px 0;
}

h4 {
  margin-bottom: 5px;
}

table {
    border-collapse: collapse;
    width: 100%;
}

th, td {
    text-align: left;
    padding: 8px;
}

tr:nth-child(even){background-color: #f2f2f2}

th {
    background-color: #4CAF50;
    color: white;
}

Tip

I found a rake task that exports data from the database and creates seeds.rb.

namespace :export do
  desc "Prints Article.all in a seeds.rb way."
  task :seeds_format => :environment do
    Article.all.each do |article|
      puts "Article.create(#{article.serializable_hash.delete_if {|key, value| ['created_at','updated_at','id'].include?(key)}.to_s.gsub(/[{}]/,'')})"
    end
  end
end

This will make it easy to download the source code and run it on your machine.

References


Related Articles

Watch this Article as Screencast

You can watch this as a screencast Consuming JSON API using jQuery 3.2.1, Vue.js 2.3.3 in Rails 5.1