Setting Up A RESTful API With Ruby on Rails

Ruby on Rails is an excellent choice for building a REST API, stemming from its design principles and robust feature set. It encourages a resource-oriented architecture, including built-in routing, migrations, and task tools.

Rails also includes Active Record, an Object-Relational Mapping (ORM) layer, which simplifies database interactions. Rails 5 and later versions allow you to create API-only applications, which are slimmed down compared to full-stack Rails applications and optimized for API functionality.

So, let’s do just that.

Setting Up Our Rails API Project

We will set up a simple RESTful API that allows us to create, read, update, and delete book information.

The first thing we need to do is set up a Rails API project. To do that, we use the —api flag during project creation:

rails new books_api --api

The rails new command generates a new Rails application called books_api. The--api flag configures this new Rails project as an API application. This includes the necessary initial directory structure and files for a Rails application but tailored to API development, omitting elements that are typically used for full-stack web applications, expressly:

- Omits Middleware and Views: It configures Rails to exclude unnecessary middleware for an API, such as those for handling cookies and sessions. It also skips views and assets because these are typically unnecessary for an API.

- Configures Generators: It modifies the behavior of generators to skip views, helpers, and assets when scaffolding a new resource.

- Default to API Mode: In this mode, Rails applications inherit from ActionController::API instead of ActionController::Base. This makes the application controller lighter and more suitable for handling API requests.

This setup is ideal for developing applications where the backend is solely intended to provide an API, often consumed by a frontend application or service running on a different platform or framework.

Next, we need to build our models:

rails generate model Book title:string author:string

This generates a Ruby class file (book.rb) in the app/models directory. This file represents the Book model, defining an object in the application corresponding to a database table. The title:string and author:string parts of the command specify the columns and their data types that will be added to the books table. In this case, two columns (title and author) of type string will be created.

Behind the scenes, this will also create a table named books with columns title and author of the string data type.

We then run:

rails db:migrate

This applies our database migrations. This command is crucial to managing the database schema in a Rails project.

Next, we need to create our controller. The controller is a central piece in Rails's MVC (Model-View-Controller) architecture. In the controller, you define actions to interact with models, process data, and determine the appropriate response (such as rendering a view or redirecting to another action). We create our books controller like this:

rails generate controller Books

This creates a books_controller.rb controller file in app/controllers/. This file is a Ruby class where we can define API methods like index, show, and create, corresponding to different RESTful resource parts.

This also creates a corresponding view in app/views/. For a regular application, you place the template files for the views the controller actions will render. However, you usually won’t need view templates for an API-only application.

API-only applications focus on processing requests and sending back JSON (or other formats) responses rather than rendering HTML views. Therefore, while the structure remains the same, implementing actions within the controller focuses on data processing and API response generation.

Seeding the database

As part of our API, we’ll have a way to add data to our app. However, we also want to have some data already seeded to build out read functionality first. Ruby on Rails allows you to seed information into the app database quickly. In your project, go to db/seeds.rb and add:

# Add some sample books
Book.create(title: "The Great Gatsby", author: "F. Scott Fitzgerald")
Book.create(title: "1984", author: "George Orwell")

We can then run a seed script:

rails db:seed

Adding Our API Endpoints

We now have all the files needed for our project. The next step is to add the methods for each API route. Let’s start with a basic GET endpoint that lists everything in our database.

In app/controllers/books_controller.rb, set up a basic action to return all books:

class BooksController < ApplicationController
  def index
    @books = Book.all
    render json: @books
  end
end

This defines our BooksController class, which inherits from ApplicationController.

When a GET request is made to the index action (via /books URL), the BooksController#index action is invoked. It retrieves all book records from the database and renders them as JSON responses.

Let’s go through a couple of the points here:

- index is a conventional name for an action in Rails controllers that is meant to display a list of all records for a particular resource, in this case, books. This action is mapped to the HTTP GET request by Rails' routing conventions, accessed via a URL like /books.

- @books = Book.all fetches all the records from the Book model and stores them in an instance variable @books.Book.all is an Active Record method that retrieves all records from the books table in the database.

- render json: @books sends a response back to the client. It converts the @books object, which contains a collection of book records, into JSON format.

Let’s see this in action. We start the rails server with:

rails server

Then, we can go to http://localhost:3000/books using an API tool like Postman. Choose GET as the method:

There, we’ll see the JSON response, which includes information about our two books.

This is basic. For our CRUD, we need:

- A way to create new books: We can add a create action to our BooksController that handles a POST request to /books. This action will receive the book parameters from the request, create a new book record in the database, and return the newly created book as a JSON response.

- A way to view a single book: We can add a show action to our BooksController that handles a GET request to /books/:id. This action will retrieve a specific book record from the database based on the provided id parameter and return it as a JSON response.

- A way to update a book: We can add an update action to our BooksController that handles a PUT request to /books/:id. This action will update the book record in the database based on the provided id parameter and book parameters from the request and return the updated book as a JSON response.

- A way to delete a book: We can add a destroy action to our BooksController that handles a DELETE request to /books/:id. This action will delete the book record from the database based on the provided id parameter and return a JSON response indicating the success of the deletion.

Create / POST

Let’s start with a POST endpoint, as this is critical for getting data into our application:

# New create action
  def create
    @book = Book.new(book_params)
    if @book.save
      render json: @book, status: :created
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end

We use the create method to handle the creation of new records. This is an action within a Rails controller specifically designed to handle the creation of a new Book record. When a POST request is made to the controller (to the URL /books), this create action is invoked.

Within this action, we have @book = Book.new(book_params). This line initializes a new instance of the Book model with parameters received from the request.

If the book is successfully saved, a JSON representation of the newly created book will be returned to the client. If the book is not saved (due to validation errors, for instance), we send back the errors as JSON.

We use book_params to prevent mass assignment vulnerabilities. It is a private method call that uses strong parameters, only allowing us to create the title and the author:

private

  # Strong parameters for book creation
  def book_params
    params.require(:book).permit(:title, :author)
  end

So, when a POST request is made to the /books endpoint with book data, the create action in the controller is invoked.

This action creates a new Book with the provided parameters.

Show / GET

Above, we showed how to GET a list of all books. But often in database applications, you only want a single entry or subset of the dataset. Here is our GET route:

  # ... index, create actions here

before_action :set_book, only: [:show, :update, :destroy]
  # GET /books/:id
  def show
    if @book
      render json: @book
    else
      render json: { error: "Book not found" }, status: :not_found
    end
  end

The before_action method ensures that a private method set_book is called before showing, updating, and destroying actions. It sets the @book variable based on the id parameter from the request.

The show action/method within the controller responds to GET requests made to a URL like /books/1, where 1 is the id of the book. If @book is present, we send a JSON response to the client with the book details. If not, we send an error message.

We need to add the set_book private method:

private
# Use callbacks to share common setup or constraints between actions
  def set_book
    @book = Book.find_by(id: params[:id])
  end

# ... other private methods

This method serves as a callback to set up a common setup or constraint shared across multiple actions within the controller. It uses the find_by method provided by ActiveRecord to retrieve a record based on the specified conditions. Here, it searches for a book whose id column matches the id provided in the URL parameters (params[:id]).

So if we call a GET request to http://localhost:3000/books/1, we get:

Update / PATCH

Being able to update entries is an integral part of a database application. The PUT/PATCH HTTP methods are designed for this and correspond to the update method in Ruby on Rails. Here is our action:

# PATCH/PUT /books/:id
  def update
    if @book.update(book_params)
      render json: @book
    else
      render json: @book.errors, status: :unprocessable_entity
    end
  end

This defines the update method in the controller, which is invoked when a PATCH or PUT request is made to a URL like /books/:id .@book.update(book_params) then attempts to update the @book instance with the parameters passed from the request. Like with the GET method, @book is set by the before_action callback method in the controller.

If the update is successful (@book.update(book_params) returns true), a JSON of the updated book will be sent back to the client.

We can then see this updated information with a GET call:

Destroy / DELETE

Our delete action is the easiest:

# DELETE /books/:id
  def destroy
    @book.destroy
    head :no_content
  end

The destroy action in the BooksController is responsible for handling the deletion of book records. When a DELETE request is made to /books/:id, this action is invoked to delete the book with the specified id from the database.

If we use DELETE with http://localhost:3000/books/3, we don’t see much, but if we try a GET on that entry again, we can see it has been deleted:

Easy RESTful APIs with Ruby on Rails

Ruby on Rails offers a streamlined and efficient approach to building RESTful APIs. Emphasizing convention over configuration, Rails simplifies creating, reading, updating, and deleting resources, allowing developers to focus more on business logic rather than boilerplate code. The framework's comprehensive support for RESTful routes, combined with ActiveRecord for database interactions and strong parameters for security, ensures that API development is secure and straightforward.

Whether you're a seasoned developer or just starting, Rails' intuitive design and rich ecosystem make it an ideal choice for developing robust and scalable RESTful APIs with minimal hassle.