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.