Ruby on Rails is a great framework to build web applications. A great share of websites and services use it nowadays, including GitHub, Airbnb and Shopify. Since its introduction 14 years ago, it has evolved into a mature, flexible and extremely powerful tool to develop entire web services. Moreover, the community around it is huge, meaning documentation and libraries are easy to find.
What types of projects is Ruby on Rails good for?
Rails was initially built to build “CRUD” web applications. CRUD stands for Create, Read, Update, Delete. Over the course of its history, it gained more and more features, from WebSockets to add real-time updates to your apps, to a full WYSIWYG text editor.
Even though it’s not primarily used to build APIs, it most certainly can be. In fact, Rails provides an “API mode”, where the initially created app only loads the necessary components to run an API — no view rendering, for example — to save resources. Of course, you can run a regular web app and an API at the same time. The framework allows you to do this in various ways. We want to explore one way to achieve this. We find that the method we’ll show here gives you a nice encapsulation of concerns, separating the actual “GUI” from the API endpoints.
Converting an existing app into an API
For this article, we’ll be starting with an already existing project. This project is a simplified Twitter clone, where users can post “microposts”. The project already has implemented a database structure, controllers, routes, views, etc. This will allow us to focus more on converting the app to an API, rather than just starting from scratch. What we’ll do is add some API endpoints, some basic authentication for them, and then deploy our app to Heroku. This will allow us to provide our API through RapidAPI, which we’ll also show how to achieve. Let’s get started.
Prerequisites
First and foremost, you will need Ruby. To install it, you can follow the official indications, depending on your platform. You will also be needing Yarn, as the project depends on it. Installing also depends on your platform and will be easier if you just follow their official instructions. Once you’ve done that, clone the starting project. We’ll be using the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails by Michael Hartl. We hardly recommend this tutorial, if you wanna learn the inners of Rails. Go ahead and clone the project:
git clone https://github.com/mhartl/sample_app_6th_ed.git cd sample_app_6th_ed
Now, we’re just going to follow the project’s setup instructions:
yarn add jquery@3.4.1 bootstrap@3.4.1 bundle install --without production
Run some extra commands, to, respectively, migrate the database, run some automated tests to check that everything and seed the database with some example data:
rails db:migrate rails test rails db:seed
Finally, start the rails server by running rails server
and point your browser to http://localhost:3000. You should be greeted with the home page of our fake Twitter site:
Step 1: API Module And Authentication
Like we mentioned before, we’d like to split our API code from the Web code. To do this, we’ll create a module for our API controllers. Create a new directory in the app/controllers
folder called api
. Inside this folder, we’ll create an extra directory called v1
. The idea behind this is to have a module for each version of the API. In case you need to create a new version, you can just create a new module with the new code. After creating the directories, add a new controller called api_controller.rb
. This will be our parent controller for the API, and will contain authentication code, among other utilities:
module Api module V1 class ApiController < ActionController::Base before_action :check_basic_auth skip_before_action :verify_authenticity_token private def check_basic_auth unless request.authorization.present? head :unauthorized return end authenticate_with_http_basic do |email, password| user = User.find_by(email: email.downcase) if user && user.authenticate(password) @current_user = user else head :unauthorized end end end def current_user @current_user end end end end
The code here is fairly simple. We’re declaring a new controller in the Api::V1
module. Here, we’ll require that the check_basic_auth
method is called before any action, and we’re also requiring we skip the CSRF token verification since this is an API. Next, we define our authentication check method. We’re checking that a Basic Authorization HTTP header is actually present, otherwise, we immediately render an empty response and return a 401 status code by calling head
. In case the header is provided, we can use Rails’ authenticate_with_http_basic
utility to check the user’s authorization. Here, we find the user via their email and check that the password is correct. If it is, we’ll set an instance variable @current_user
with the user model, otherwise, we’ll also respond with a 401 status. Our last method is just a convenience method to fetch the current user’s instance.
A note here: This whole authentication ordeal will greatly depend on your app and how you’ve chosen to setup authentication in it. This particular project uses has_secure_password
in the User model for this purpose, but you could also just as easily chosen to use the Devise gem for authentication. Also, for a bigger app, it might make sense to implement the use of API keys or something similar. We’ve chosen to use HTTP Basic Authentication here because it’s simple. You might also notice that with this approach, every method in our API will require authentication (even the ones that should be public like the main feed). This is again for simplicity, but this could actually make sense in a regular API, since this way you can more easily avoid API abuse, for instance.
Step 2: Routing and Microposts
With us so far? Great! Now we’re going to implement our API microposts controller. We’re going to implement three methods, mapping to three endpoints. A listing endpoint, to render the user’s main feed, a detail endpoint, which will only respond with a single micropost, and a create endpoint, so users can create microposts with the API. First, let’s create the routes we need. In the config/routes.rb
file, add this:
namespace :api do namespace :v1 do resources :microposts, only: [:index, :show, :create, :destroy] end end
Notice the use of namespace
. This allows us to use modular controllers and translates to a url like /api/v1/microposts
for example. Now, let’s create the index
method. Create a new file in app/controllers/api/v1/microposts_controller.rb
and add this to it:
module Api module V1 class MicropostsController < ApiController def index @microposts = current_user.feed.paginate(page: params[:page]) end end end end
Pretty simple so far: we fetch the current_user’s feed (the microposts of the users they’re following), and paginate it. Here, we’re leveraging the fact that the project already has the will_paginate gem installed, to paginate our response. Now, to render our JSON response, we’ll be using yet another gem that’s pre-installed: jbuilder. This is a fairly powerful library that simplifies building JSON responses. Create new directories in the app/views
folder: app/views/api/v1/microposts
. Yes, our JSON responses are also “views” for all intents and purposes. Here, create a file called index.json.jbuilder
:
json.pagination do json.current_page @microposts.current_page json.per_page @microposts.per_page json.total_entries @microposts.total_entries end json.microposts @microposts do |micropost| json.id micropost.id json.user do json.id micropost.user.id json.name micropost.user.name end json.content micropost.content json.created_at micropost.created_at end
The syntax might be a little off-putting at first. We recommend you give the Jbuilder gem’s documentation a quick read. We’re starting by adding some information about pagination. By calling json.pagination
we’re creating a pagination key in the JSON response and everything inside the block will be inside this object. You’ll see what we mean when you look a the JSON response. After that, we’re looping over the microposts and rendering some data about each one.
To look at how this endpoint behaves, we need to call it. First, you’ll need a way to interact with the API (there’s no GUI for an API!). If you’re not comfortable with the terminal, we recommend looking into Postman or Insomnia or Paw, which are pretty good apps for using APIs. In this article, we’ll limit ourselves to using curl
. Before that though, we need a user! For this, you can either sign up on the app and follow some of the fake users, or just use the default test user: example@railstutorial.org
with password foobar
. Run this command on your terminal:
curl --user "example@railstutorial.org:foobar" http://localhost:3000/api/v1/microposts
With curl, we specify the username and password as email:password
. If you’re using one of the apps we recommended above, look for “HTTP Basic Authentication”. Now, let’s take a look at part of the response:
{ "pagination": { "current_page": 1, "per_page": 30, "total_entries": 306 }, "microposts": [ { "id": 309, "user": { "id": 1, "name": "Example User" }, "content": "Lorem Ipsum Dolor Sit Amet.", "created_at": "2020-03-15T12:22:02.397Z" }, ... ] }
Let’s focus on the pagination part first. Like we specified in the view before, there’s a pagination
key that contains the current page, the number of microposts per page, and the total amount of entries. Then, we have each micropost, with the fields we specified. Notice the user data is embedded in the user
key. Compare this to the jbuilder view we made, and you’ll get a quick idea of how this works.
Step 3: Details endpoint
Let’s implement the show
endpoint now. Add this method to your microposts controller:
def show @micropost = Micropost.find(params[:id]) end
Nothing much going on here. We just fetch the micropost by their id (no such thing as private microposts in this API). What we’re going to do now is use a partial to render the micropost, and then reuse that partial in the list endpoint, to avoid code duplication. Create a new file in app/views/api/v1/microposts/_micropost.json.jbuilder
:
json.id micropost.id json.user do json.id micropost.user.id json.name micropost.user.name end json.content micropost.content json.created_at micropost.created_at
This is more or less the same view as the one we used for the list endpoint, in Step 2. Now, create a new file in app/views/api/v1/microposts/show.json.jbuilder
:
json.partial! 'api/v1/microposts/micropost', micropost: @micropost
This loads the partial we created earlier and sets the local micropost
variable to the value of @micropost
which we set in the controller. Make sure you edit the index.json.jbuilder
view as well, to use the partial:
json.pagination do json.current_page @microposts.current_page json.per_page @microposts.per_page json.total_entries @microposts.total_entries end json.microposts @microposts, partial: 'api/v1/microposts/micropost', as: :micropost
Here we’re using the partial to render a collection, hence the slightly different syntax. We tell jbuilder to render a microposts
key, and render our collection of @microposts
using the partial, setting the local variable micropost
to each of the values in our collection. You can try the same endpoint as before to make sure it still works the same. Let’s also try the details endpoint:
curl --user "example@railstutorial.org:foobar" -H "Accept: application/json" http://localhost:3000/api/v1/microposts/200
This should render a single micropost:
{ "id": 200, "user": { "id": 2, "name": "Chantal Rowe Sr." }, "content": "Placeat numquam doloremque et nostrum.", "created_at": "2020-03-14T15:21:33.184Z" }
Step 4: Deploying to Heroku
Since this article is turning out to be long enough already, we’re going to leave the implementation of the rest of the endpoints as an exercise. Note that the create
endpoint is nothing more than a similar endpoint for what already exists in the “visual” part of the Rails app, with the same renderer as the show
endpoint.
To deploy to Heroku, you’ll need an account and the Heroku CLI. After you’ve signed up and installed the CLI utility, we’re ready to get going (make sure you’ve ran heroku login
). There are a bunch of things we need to do for our sample app to work on Heroku. First, since this is just a simple test app, we need to force Active Storage (a Rails utility to store files) to use the local file system. This is, of course, discouraged since Heroku’s file system is ephemeral and deletes all files once your process restarts. For this article, however, that’s fine. You can refer to this article to configure your app correctly. For now, edit the file config/environments/production.rb
and change the line that reads config.active_storage.service = :amazon
to read config.active_storage.service = :local
.
Now, commit all your changes so far. This is necessary because Heroku uses git as a transfer method. Run:
git add . git commit -m "Convert app to API"
Now, we’re going to execute a series of commands, which we’ll explain after:
heroku create heroku addons:create heroku-postgresql:hobby-dev git push heroku master heroku run rails db:migrate heroku run rails db:seed
First, we create a new app in heroku. You should get the name of your app, something like aqueous-sierra-12345
. Then, since our app relies on a database, we create an addon on our app, in this case, the free version of the Postgres database Heroku provides. Now, we push our app. This command will take a while because Heroku needs to install and configure all our dependencies. After that command’s done, we migrate our database and seed it (this last step is optional, of course). You should now run heroku open
which will open your browser and point it to your app. You should be able to sign up, login and see the microposts.
The API you created can be used as well. Just do the same you did before, but instead of pointing to localhost:3000, point your curl
commands to the URL of your Heroku deployment.
Step 5: Publishing on RapidAPI
To publish your API on RapidAPI, you’ll need an account. Go ahead and sign up if you haven’t already. Now, at the top, click on “My APIs”, then on the left, click on “Add New API”.
Fill out the form with some details, and make sure you select “UI” for how we’ll specify our API. Click “Add API”.
Now, click on “Add a base URL”. In the Base URL field, enter the URL for your Heroku app, but make sure you prepend /api/v1
to it, since we’re only publishing our API, not the whole website. Also, below, add two transformations, like in the image. This way our API always accepts and responds with JSON:
Next, click on “Add some endpoints” at the top, then click on “Create Endpoint”. First, enter a name and a description for this endpoint. Then enter the endpoint: /microposts
. In this case, the list microposts endpoint. Below, click on “Header” and enter “Authorization” in the name, and “Basic base64_email_and_password” as the value. Set it to required.
Above, click on “Query”, and create a new query parameter. Enter page
for the name, 1
for the value, and leave the rest as is. Remember that we were offering pagination? This is how the users can use it. Now click on “Save”.
Now, in the list of endpoints, click on “Copy” for the endpoint we just created. Change the name and description to match our show
endpoint, then change the endpoint to be /microposts/{id}
. The curly braces tell RapidAPI this is a parameter. Go to the “Query” tab, and make sure you delete the page parameter since it doesn’t make sense for this endpoint. Click “Save”. You’re pretty much done!
Step 6: Test your API in RapidAPI
On the top right, click on “View in Marketplace”. You’ll be taken to the public version of your API in the RapidAPI Marketplace. Don’t worry: only you can see this page for now. Let’s test the listing endpoint. You’ll need your email and password to authenticate with your API. Only one detail, they need to be Base64 encoded. This is how Basic HTTP Authentication works. You can use ruby for this. Run irb
in your terminal and then do:
require 'base64' puts Base64.encode64('<EMAIL>:<PASSWORD>')
So, for our test user, you’d do puts Base64.encode64('example@railstutorial.org:foobar')
to get something like ZXhhbXBsZUByYWlsc3R1dG9yaWFsLm9yZzpmb29iYXI=
. This is your credential. So, to test your endpoint, replace “base64_email_and_password” in the Authorization field with that, then click on “Test Endpoint”:
You should be able to see the results of the query on the right!
Conclusion
We hope this article gave you the tools you need to convert an existing Rails app into an API. Albeit short, creating the rest of the endpoints shouldn’t be much more different than what we’ve shown you. We also showed you how you can publish your API via RapidAPI, where you can potentially monetize it.
Leave a Reply