Create a Basic API with Ruby on Rails
Tags: rails, ruby, tutorial, video
Published: 2021-03-30 14:14:55

---
title: Create a Basic API with Ruby on Rails
published: true
date: 2021-02-12 16:32:13 UTC
tags: rails, ruby, tutorial, video
canonical_url: https://web-crunch.com/posts/create-a-basic-api-with-ruby-on-rails
---

Welcome to a new ongoing tutorial mini-series dedicated to building an API-driven Ruby on Rails application.

The goal of this series is to give some perspective of what it's like building a backend API that can talk to multiple applications no matter the platform.

I plan to discuss topics like returning data to a client, routing, versioning, authentication, and more.

**Consider this series a work in progress**. If you have questions/suggestions on what I should cover please feel free to comment below or [reach out to me](https://web-crunch.com/contact/new).

#### Part 1

{% youtube 3S9fyfmCf1A %}

#### Part 2

{% youtube MTnqWxr6djE %}


## Part 1 Notes

In part one, I kick off the new application in `--api` mode which is new to Rails since version 5.2. This mode removes all the view related components on a new rails app when running `rails new` on the command line.

You can kick off the app by running:

```bash
rails new band_api --api

```

This generates a new app that assumes all your data will be returned as `JSON` data by default. It's worth noting that you can display both HTML and JSON data by default in a vanilla Rails app. The API mode just cuts down on the cruft should you not need HTML responses. You might use the API mode if your frontend is entirely built with React or similar.

Most of the time I like to mix and match here. I'll keep most of a Ruby on Rails application vanilla with sprinkles of more javascript driven components in the app. You can return JSON for those specific components and HTML everywhere else. This allows you to not re-invent the wheel so much in my opinion.

#### Creating a resource

This example API will be about Bands, band members, and albums. To start we need a band resource.

```bash
rails g scaffold Band name:string

```

The band model simply needs a name column for now.

While we are at it you can create a `Member` resource that associates itself to the `Band` model

```bash
rails g model Member name:string band:references

```

A member will have a `band_id` and `name` column. The band ID is how we associate a member to a band.

```bash
rails db:migrate

```

With that out of the way, we can ensure our models are set up correctly.

```ruby
# app/models/band.rb
class Band < ApplicationRecord
  has_many :members
end


# app/models/member.rb
class Member < ApplicationRecord
  belongs_to :band
end
```

Because we scaffolded the `Band` resource we have controllers in the `app/controllers` directory automatically.

```ruby
# app/controllers/bands_controller.rb
class BandsController < ApplicationController
  before_action :set_band, only: [:show, :update, :destroy]

  # GET /bands
  def index
    @bands = Band.all

    render json: @bands
  end

  # GET /bands/1
  def show
    render json: @band
  end

  # POST /bands
  def create
    @band = Band.new(band_params)

    if @band.save
      render json: @band, status: :created, location: @band
    else
      render json: @band.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /bands/1
  def update
    if @band.update(band_params)
      render json: @band
    else
      render json: @band.errors, status: :unprocessable_entity
    end
  end

  # DELETE /bands/1
  def destroy
    @band.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_band
      @band = Band.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def band_params
      params.require(:band).permit(:name)
    end
end
```

Notice how clean it is since we are just returning JSON data only.

On the `index` action we query for `Band.all` and assign it to an instance variable called `@bands`. That will automatically return a `JOSN` object containing all bands.

First, you need to create some!

```bash
rails console

Band.create(name: "AC/DC")
Band.create(name: "The Beatles")
... # add more here if you want
```

With some data in the app you should be able to boot up your server and visit the url to see JSON data in your browser. I used Firefox which returns JSON automatically.

```bash
rails server
```

Visit [localhost:3000/bands/](localhost:3000/bands/)

#### Returning JSON

Rails returns JSON by default if you want it to. In a default Rails app without `--api` mode enabled you can leverage [jbuilder](https://github.com/rails/jbuilder) which is a really awesome DSL (domain specific language) for generating JSON data while writing Ruby code. It's very simple to write and makes a lot of sense to use if you're not working on a backend-only application.

Sometimes you want more control in what returns. This might be required for generating rich relationships between models for example. Getting that sophisticated with your JSON might be best left for some third-party gems like [active-model-serializers](https://github.com/rails-api/active_model_serializers).

There are more modern approaches out in the wild for handling this but I find this gem to be pretty straight-forward to the newcomer.

```bash
bundle add active_model_serializers

```

With the gem add to your app you now have access to a new generator type called `serializer`. Think of this as almost another model in your app that allows you to intercept what data returns in your app without writing JSON directly.

```bash
rails generate serializer band

```

This creates a new folder in `app/` called `serializers` and also adds a file called `band_serializer.rb`.

That file looks like this upon first creating it.

```ruby
class BandSerializer < ActiveModel::Serializer
  attributes :id
end

```

In our controller, we are returning every column at the moment. This serializer can help us dictate what we return as well as help use return members associated with bands in a sub-object.

```ruby
# app/serializers/band_serializer.rb
class BandSerializer < ActiveModel::Serializer
  attributes :id, :name

  has_many :members
end
```

To return real members you'll need to create some data.

```bash
rails console

Member.create(name: "Angus Young", band_id: 1)
```

If you recall the bands you created previously, here is where you need to assign their IDs manually when creating a new member. You can always return a list of the bands in your database by running.

```bash
rails c
Band.all
```

Take note of each Object's `id` column when you are creating members for each Band.

With Member data we now need a serializer for members much like bands

```bash
rails generate serializer Member
```

```ruby
# app/serializers/member_serializer.rb
class MemberSerializer < ActiveModel::Serializer
  attributes :id, :name
  belongs_to :band
end
```

Now revisiting [localhost:3000/bands](localhost:3000/bands) should return both band and member data in rich JSON format!

#### Scaling

Sometimes JSON data can get out of hand pretty fast. This is especially true in a team setting. With this in mind there are protocols/specifications that exist which allow you to return data in a agreed upon style.

[JSON API](https://jsonapi.org/) is an example of one of these specifications. Below is an example copied from their website for context:

```json
{
  "links": {
    "self": "http://example.com/articles",
    "next": "http://example.com/articles?page[offset]=2",
    "last": "http://example.com/articles?page[offset]=10"
  },
  "data": [
    {
      "type": "articles",
      "id": "1",
      "attributes": {
        "title": "JSON:API paints my bikeshed!"
      },
      "relationships": {
        "author": {
          "links": {
            "self": "http://example.com/articles/1/relationships/author",
            "related": "http://example.com/articles/1/author"
          },
          "data": { "type": "people", "id": "9" }
        },
        "comments": {
          "links": {
            "self": "http://example.com/articles/1/relationships/comments",
            "related": "http://example.com/articles/1/comments"
          },
          "data": [
            { "type": "comments", "id": "5" },
            { "type": "comments", "id": "12" }
          ]
        }
      },
      "links": {
        "self": "http://example.com/articles/1"
      }
    }
  ],
  "included": [
    {
      "type": "people",
      "id": "9",
      "attributes": {
        "firstName": "Dan",
        "lastName": "Gebhardt",
        "twitter": "dgeb"
      },
      "links": {
        "self": "http://example.com/people/9"
      }
    },
    {
      "type": "comments",
      "id": "5",
      "attributes": {
        "body": "First!"
      },
      "relationships": {
        "author": {
          "data": { "type": "people", "id": "2" }
        }
      },
      "links": {
        "self": "http://example.com/comments/5"
      }
    },
    {
      "type": "comments",
      "id": "12",
      "attributes": {
        "body": "I like XML better"
      },
      "relationships": {
        "author": {
          "data": { "type": "people", "id": "9" }
        }
      },
      "links": {
        "self": "http://example.com/comments/12"
      }
    }
  ]
}
```

This returns the data in a way a team or group of developers can expect. When you use a spec it's a _source of truth_ so there are no surprises along your software building journey.

What's super cool is that the `active_model_serializers` gem comes with support for this format out of the box. You simply need to add a configuration setting to your Rails app and you're done.

To add it you can create a new initializer file (name it whatever you prefer) in `config/initializers`

```ruby
# config/initializers/ams.rb

ActiveModelSerializers.config.adapter = :json_api
```

That then returns your data in the JSON API recommended spec. So cool!

## Part 2 Notes

Routing in an API driven app is very important. Often times you want to version your API as well so if someone is using it and a bit behind on versions, their code won't simply break if you make changes.

With Rails, adding an API routing pattern is quite trivial. Here's the final pattern in use at the end of this part.

```ruby
# config/routes.rb
Rails.application.routes.draw do

  namespace :api do
    namespace :v1 do
      resources :bands do
        resources :members
      end
    end

    namespace :v2 do
      resources :members
      resources :bands
    end
  end
end
```

Usually declaring a namespace wrapper around your entire API is wise. This ensures anything within is matched to the `api` itself which makes for easier debugging as errors arise.

Versioning your API is a good convention to uphold as well. Most APIs in the wild follow this pattern as it makes updates for those who aren't ready for a new version you're releasing able to still use legacy versions.

Rails make namespacing and creating restful routing quite easy. You can even have basic routing for your normal rails views combined with a namespaced API in the same app following the monolith approach many Rails apps know and love.