Introduction

This is part one of a four part tutorial on building a Create-Read-Update-Delete (CRUD) application with a React front end and a Ruby on Rails back end. Each section is self-contained. This section will show you how to build an Application Programming Interface (API) with Ruby on Rails. The next section will show you how to build a separate React application to serve as the user interface, interacting with the API to get and set data. The third section will show you how to create the same application integrating React directly into the Rails application using the Webpacker gem. The fourth will add a User model and authentication using JSON Web Tokens.

This section, part 1, is an introductory tutorial on Rails APIs. It assumes you are already familiar with Ruby on Rails. Essentially we are building an application based on the Ruby on Rails Model-View-Controller architecture but without the view. Instead we will provide URL endpoints that interact with a separate front-end application. In this case the front end is a React Web interface, but it could be a different library or framework like AngularJS or Vue.js or a mobile app.

Quick note on terminology. In this tutorial the terms back end, server side, and API all refer to the Rails API and can be used interchangably. The terms front end, client, user interface, and React views in the context of this tutorial all refer to the React components and can also be used mostly interchangably. Throughout this tutorial we will preface Terminal commands with a dollar sign (but don't enter the dollar sign). Code you enter in files will start with "#" and the file name at the top

What type of API do you need?

Determining what type of API you need depends on what type of front end client or clients your app will interact with. In the past, APIs were intended for external web sites to access data from your site, usually read only. Then smartphones came on the scene and suddenly millions of mobile apps required access to data through APIs with both read and write capabilities. Now with JavaScript libraries and Frameworks like React, Angular, Vue and others acting as separate front-end applications, frameworks like Rails need to provide APIs just for their own front end. And even then you have the option of using the front-end application exclusively for all your views as with a Single-Page Application (SPA). Or you can use something like React for some of your front-end components and Rails views for the others. Today APIs are used for all these types of clients and more.

Some of the ways you can set up an API for these needs include:

The Webpacker Gem

Traditionally, if you wanted to use a front-end library like React with your Rails app you would create a completely separate front-end application. This is fine for a SPA, but if you want to use React for only some of your views how would you do it? Rails 5.1 and up ships with a gem called webpacker that allows you to integrate front-end libraries like React, Angular, and Vue directly into your Rails app. Actually, the Webpacker gem is a middleware between Rails and the popular Webpack JavaScript pre-processor and bundler. We will cover Webpacker in Part 3 of this tutorial.

JSON

The must common format for transmitting data to and from APIs is JavaScript Object Notation (JSON). JSON is not actually JavaScript. It is just a text string that is in the format of JavaScript objects, with attribute-value pairs separated by colons and surrounded by curly braces. Multiple objects are grouped in square brackets just like JavaScript arrays. JavaScript has built-in methods for converting JSON stings into JavaScript objects and JavaScript objects into JSON strings.

Endpoints and the HTTP Request-Response cycle

Your Rails API needs to provide endpoints to interact with the client. The endpoints are the URLs that the client calls with an HTTP request. Besides the URL, the request includes other information such as the HTTP method (GET, POST, PUT, PATCH, or DELETE). And for POST, PUT and PATCH requests it will include your data in the body, and the content type to indicate what format the data is in, in our case JSON. Each URL endpoint will have a corresponding controller action. That's where you program the action to take, often interacting with the database to get or set data. Your server then sends back a response to the client. If it was a GET request, the response would be the requested data in a JSON string. Your server also sends a status code back such as 200 for success, 404 for page not found, or 500 for internal server error to name a few.

The finished code for Part 1 and 2 combined can be found on Github


Build a Traditional Web Application that includes an API

Okay, let's build our Ruby on Rails API. Our application will include a database that holds articles that users can read, write, edit or delete. Rails ships with JSON API capability right out of the box. When you generate a scaffold it creates a controller with both html and JSON format. The JSON format can be used for an API. Let's use it.

Create a new Rails app

  • mkdir rails-api-with-react; cd rails-api-with-react - Make your application folder and change directories into it.
  • If you are using RVM for Ruby and Gems version control you can set your gemset at this point.
  • rails new . - Generate the app.

    Generate an Articles scaffold

  • rails generate scaffold Article title:string content:text --no-assets --no-helper
  • rails db:migrate
  • This will generate an articles database table, an Article model, a set of RESTful routes, an articles_controller, and HTML and JBuilder views. The latter is the serializer converting your data to JSON strings.

    Controller

    The controller that the scaffold generated responds to both HTML and JSON formats. As a convenience, it lists the URL paths as comments above each action. The index, show, create, update, and destroy actions all allow JSON format requests. Rails treats the request as an HTML request by default unless the URL has .json appended to it.

    Below is the scaffold generated controller. We'll use it as is except we need to add the line protect_from_forgery unless: -> { request.format.json? } at the top. Alternatively, we could put this line in the application_controller.rb file so it applies to all controllers. We'll discuss this in the CORS and Security section shortly.

    # app/controller/articles_controller.rb
    class ArticlesController < ApplicationController
      protect_from_forgery unless: -> { request.format.json? }
    
      # GET /articles
      # GET /articles.json
      def index
        @articles = Article.all
      end
    
      # GET /articles/1
      # GET /articles/1.json
      def show
        @article = Article.find(params[:id])
      end
    
      # GET /articles/new
      def new
        @article = Article.new
      end
    
      # GET /articles/1/edit
      def edit
        @article = Article.find(params[:id])
      end
    
      # POST /articles
      # POST /articles.json
      def create
        @article = Article.new(article_params)
        respond_to do |format|
          if @article.save
            format.html { redirect_to @article, notice: 'Article was successfully created.' }
            format.json { render :show, status: :created, location: @article }
          else
            format.html { render :new }
            format.json { render json: @article.errors, status: :unprocessable_entity }
          end
        end
      end
    
      # PATCH/PUT /articles/1
      # PATCH/PUT /articles/1.json
      def update
        @article = Article.find(params[:id])
        respond_to do |format|
          if @article.update(article_params)
            format.html { redirect_to @article, notice: 'Article was successfully updated.' }
            format.json { render :show, status: :ok, location: @article }
          else
            format.html { render :edit }
            format.json { render json: @article.errors, status: :unprocessable_entity }
          end
        end
      end
    
      # DELETE /articles/1
      # DELETE /articles/1.json
      def destroy
        @article = Article.find(params[:id])
        @article.destroy
        respond_to do |format|
          format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
          format.json { head :no_content }
        end
      end
    
      private
        def article_params
          params.require(:article).permit(:title, :content)
        end
    end
    

    The index, show, create, update, and delete actions are all API end points that respond to JSON requests.

    The index and show actions respond to GET requests by pulling article records from the articles database table and rendering them. In Rails the render for GET requests is implied but you could add render :index and render :show to the index and show actions if you wanted to be explicit. For JSON requests, when using the JBuilder gem, Rails will render JSON views through the JBuilder view files. Below are the ones auto generated with the scaffold refactored to not use a partial. You can filter which attributes you want to send back and indicate the format to use. Rails then converts the data to a JSON string and transmits it back to the requester with the response.

    # app/views/articles/index.json.jbuilder
    json.array! @articles do |article|
      json.extract! article, :id, :title, :content, :created_at, :updated_at
      json.url article_url(article, format: :json)
    end
    
    # app/views/articles/show.json.jbuilder
    json.extract! @article, :id, :title, :content, :created_at, :updated_at
    json.url article_url(@article, format: :json)
    

    A POST request is handled by the create action. Rails converts the request body from a JSON string to a Ruby object and attempts to save it to the database after checking the params in the article_params helper method. If it is saved successfully, Rails will render the show method. Otherwise it returns the error messages with a status of :unprocessable_entity which corresponds to status code 422.

    PUT and PATCH requests are handled similarly as a POST request. PUT is intended to update an entire record while PATCH is intended to update only parts of a record. Rails maps them both to the "update" action so it doesn't matter which you use. However, since Rails itself is responsible for setting the updated_at attribute and created_at will not be changed, it's technically more correct to use the PATCH method.

    DELETE requests are triggered regardless of format (HTML or JSON). However, you need to specify the JSON format to get the right response, which in this case is no content at all. Otherwise it will be a redirect to the articles_url which will return a response of all the HTML from the index page.

    Seed the Database

  • Add some seed data. Something like the below.
  • # db/seed.rb
    articles = Article.create([
      {title: "Learn Ruby", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
      {title: "Learn Rails", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
      {title: "Learn React", content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}
    ])
    

    rails db:seed

    Run the Server

    We should be able to view our articles in both HTML and JSON formats.

  • rails server -p 3001 - Start the server on port 3001
  • View the articles index page at http://localhost:3001/articles
  • View the same data in JSON format at http://localhost:3001/articles.json
  • View the details of article id 3 in JSON format at http://localhost:3001/articles/3.json

  • CORS and Security

    GET requests can be made to your API but POST, PUT, and DELETE requests will return an error code of 422 Unprocessable Entity. Rails uses the same-origin security policy which forbids "cross-domain" requests by default. Cross-domain requests are requests coming from a different domain than the server, so in this case from any domain other than localhost:3001. A web application can use the Cross-Origin Resource Sharing (CORS) system to allow AJAX requests like POST, PUT/PATCH, and DELETE from a separate domain. It uses information passed with HTTP headers.

    To enable CORS we need to add the rack-cors gem to our Gemfile. This is CORS middleware for Rack compatible web applications like Rails. In the gemfile add:

    # Gemfile
    gem 'rack-cors', require: 'rack/cors'
    

    Then install it.

    bundle install

    Then configure it:

    # config/application.rb
    module ArticlesAppWithApi
      class Application < Rails::Application
        ...
        config.middleware.insert_before 0, Rack::Cors do
          allow do
            origins 'http://localhost:3000' #1
            resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :delete, :options] # 2
          end
        end
      end
    end
    

    1) Origins: whitelists what domains are allowed to make restricted requests. For any origin use '*'. Otherwise list the specific domains such as 'http://localhost:3000' or http://www.example.com.
    2) resource: specify which resource or resources the access applies to. '*' means it applies to the whole site. You can also specify which HTTP methods are allowed.

    Cross Site Request Forgery protection

    In addition to allowing CORS access above, in the controller we disabled a built-in Rails Cross Site Request Forgery protection feature when we added this statement: protect_from_forgery unless: -> { request.format.json? }.

    With this security feature, post, patch/put, and delete requests from rails forms or pages include an authenticity token. In a standard Rails app those are submitted as a hidden field with the form. But if we are using a form submitted to our API from a React front end, Rails won't generate the security token. So we need to disable that security feature for JSON requests.

    So how do you restrict who can make write requests to the API? We already are restricting what domain they can come from but what about restricting what users can access the API endpoints? There are methods to do that such as using JSON Web Tokens. We will cover that in Part 4 of this series.


    Test POST, PATCH, and DELETE with Curl

    We are done with our simple API. Well almost. We need to make sure it works. We were able to test the GET request in the browser by just entering the URL with .json appended. But how do we test the POST, PATCH and DELETE requests without having our front end built yet? We want to make sure it works before we move on. Well there are two ways we can do this. First is using Curl.

    Curl is a command line utility that lets you make HTTP requests from the Terminal instead of from a web page. Curl comes preloaded with Macs. For windows you may need to install it, but it's a handy tool sometimes. Let's make a POST request by entering the below Curl command. The -H option is for the header which is where we tell the receiver that the data we are sending is in JSON format. The -X option specifies the request method, POST in this case. The -d option is where we put our data, so you won't use the -d option with GET or DELETE requests. And of course we include the URL.

  • rails s -p 3001 Make sure you start the server if it's not already running
  • curl -H "Content-Type: application/json" -X POST -d '{"title":"Learn JavaScript","content":"Lorem ipsum."}' http://localhost:3001/articles.json

    If it posted the server should send a response back with the new Article data in JSON format. It will have the title and content fields as you submitted, but it will also have id, created_at, and updated_at fields. You can also make a Curl GET request with the new ID:

    curl -X GET http://localhost:3001/articles/4.json - change the id to the correct one if it's not 4.

    Now let's update this record with a PATCH request:

    curl -H "Content-Type: application/json" -X PATCH -d '{"title":"Learn ES6","content":"Lorem ipsum."}' http://localhost:3001/articles/4.json

    It returns a JSON string of the saved updated article data. Now, let's delete it:

    curl -X DELETE http://localhost:3001/articles/4.json

    In our controller destroy action we specified to return no content, so it won't give you confirmation that it was deleted. But, if you try to do a GET request on it will throw you an error.

    If you don't like the cryptic feel of sending HTTP requests from the command line, there are some free desktop applications that let you do the same thing from a GUI interface. Postman, Advanced Rest Client, and Insomnia are three that I'm aware of, all with similar functionality. You can download them from their respective websites and be up to speed in a short time.


    You now have a working Rails API. You can move on to the next section of this tutorial Part 2: React Front End. Or you can continue on and explore more about Rails APIs. We will briefly cover three more topics, including rebuilding our Rails app as an API only application in under five minutes.

    Active Model Serializers

    Serialization in the context of a web application is the process of converting data objects into a format like a JSON string that can transmitted across a network or the internet and deserialized back into a data object when it is received. We used the JBuilder gem as our serializer above, but there are other options. A popular one is a gem called Active Model Serializers (AMS). For reference, AMS docs are on github.com. While JBuilder approached serialization through it's view files, AMS approaches it like a model.

    Before going into the details of AMS it is worth mentioning that in February 2018 Netfix released a similar gem called fast_jsonapi that is supposed to be very fast. The concept is similar to AMS so we'll continue with the instructions for AMS since it's tried and true. You can read more about fast_jsonapi on their Github page.

    First, install the AMS gem. You can uninstall the JBuilder gem at the same time if don't use it.

    # Gemfile
    gem 'active_model_serializers'
    

    bundle install

    Then when you generate a resource or scaffold, the generator will also create an app/serializers/article_serializer.rb file. Or if you already created the article resource you can generate the serializer separately with or without specifying the attributes:

  • rails g serializer article
  • rails g serializer article title content
  • This is the file it generates:

    # app/serializers/article_serializer.rb 
    class ArticleSerializer < ActiveModel::Serializer
      attributes :id, :title, :content
    end
    

    Your controller would look something like this. In this case we are assuming you are only returning JSON format, not HTML and JSON both.

    # app/controllers/articles_controller.rb 
    class ArticlesController < ApplicationController
      protect_from_forgery unless: -> { request.format.json? }
    
      def index
        @articles = Article.all
        render json: @articles
      end
    
      def show
        @article = Article.find(params[:id])
        render json: @article
      end
    
      def create
        @article = Article.new(article_params)
        if @article.save
          render json: @article, status: :created
        else
          render json: @article.errors, status: :unprocessable_entity
        end
      end
    
      def update
        @article = Article.find(params[:id])
        if @article.update(article_params)
          render json: @article, status: :ok
        else
          render json: @article.errors, status: :unprocessable_entity
        end
      end
    
      def destroy
        @article = Article.find(params[:id])
        @article.destroy
        head :no_content
      end
    
      private
        def article_params
          params.require(:article).permit(:title, :content)
        end
    end
    

    And you no longer need the JBuilder view files. So you can delete them.

    That's it. Your API using AMS should now be working. If you start the server on port 3001 and in your browser go to localhost:3001/articles, you should get the JSON output with or without appending .json to the URL.


    API-only Applications

    Beginning with Rails 5.0 you have the option of generating an API only application instead of the traditional Rails app. A Rails API-only app is a slimmed down version of a regular Rails web app, skipping the Asset Pipeline and generation of view and helper files. You can read the details in the Rails Guides - API App.

    To generate an API-only app just add the --api flag

  • rails new appname --api
  • With an API only app you don't need to use the jbuilder or active_model_serializers gems. Rails serializes the JSON format automatically. You can generate a scaffold and it will automatically create a controller that renders JSON. And you don't need to add the protect_from_forgery unless: -> { request.format.json? } statement to your controller.

    You do still need to add the rack-cors gem as specified above.

    Rebuild the App as an API only application

    We can build the same Articles app as an API only app in less than five minutes. Ready. Go:

  • mkdir appname; cd appname
  • If you are using RVM for Ruby and Gems version control you can set your gemset at this point.
  • rails new . --api
  • rails generate scaffold Article title:string content:text
  • rails db:migrate
  • Add some seed data to db/seeds.rb
  • rails db:seed
  • # Gemfile
    gem 'rack-cors', require: 'rack/cors'
    
  • bundle install
  • # config/application.rb
    module ArticlesAppWithApi
      class Application < Rails::Application
        ...
        config.middleware.insert_before 0, Rack::Cors do
          allow do
            origins 'http://localhost:3000'
            resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :delete, :options]
          end
        end
      end
    end
    
  • rails server -p 3001
  • View the JSON data at http://localhost:3001/articles.json
  • Done!


    Add an API namespace

    Traditional web applications that also provide an API often add an "api" namespace to the URL to distinguish it from a regular URL. So instead of www.example.com/articles, the API URL would be www.example.com/api/articles. And if the API serves a mobile app, it is common to add versions to the api namespace so that changes won't break existing downloaded mobile apps. Something like www.example.com/api/v1/articles.

    There are a couple of approaches to add such a namespace to your app. First is to add a scope to the route. Something like this:

    # config/routes.rb 
    scope '/api' do
      resources :articles
    end
    

    Then the URL will include the api namespace, but the controller will not. It will just be the same controller we generated before.

    The second option is to add a namespace to the route, controller, and views (if any). This makes more sense if you are using versioning since you will then have multiple controllers.

    # config/routes.rb 
    namespace :api do
      namespace :v1 do
        resources :articles  
      end
    end
    

    The controller class name would look like this.

    # app/controllers/api/v1/articles_controller.rb
    class Api::V1::ArticlesController < ApplicationController
      ...
    end
    

    Okay, we have a working API. Or two. Now let's build a separate front-end React application to interact with it. On to Part 2