How to create a Rails API From SCRATCH

Why create an API from scratch? After all, there is a rails scaffold. A generator is a fast way to create rails applications within minutes. The downside with a generator is that you may end up with extra files, data you don’t need, adding unnecessary layers of complexity taking memory space. Also, your API will probably need to be customized. For this tutorial, I will be using one of my boot camp projects as a test case.

I Planning

To create my API I thought of the result” my user story”, who is my user? What problems will I be solving? What data types would I need, what element of the CRUD, and attributes validations will I allow or restrict to secure the data? What are my objects relationships? Do I need seed data?….

Test Case Example

My user story is to create an Open house customer relationship management. The user “real estate agent” will enter “guests” personal information, associated with houses visited, during open houses for follow-up, and ultimately add new clients to his pipeline.

II Generate Rails API

rails new my-api — api — database=postgresql

The command will create the model, route, controller files necessary to build your API. By using Postgresql instead of the default SQLite you will be able to deploy on Heroku.

III Set up Cors

You will need to install Cors to allow cross-origin requests or Cors errors will display in your dev console and you won’t be able to access the data from your API. Note that in this tutorial I did not restrict origins.

A/ Add Cors gem to your Gemfile

gem ‘rack-cors’bundle update

B/Add the code below to your cors.rb file

In your cors.rb file you can change the origins* with the URL of your choice.

//config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors doallow doorigins ‘*’resource ‘*’,headers: :any,methods: [:get, :post, :put, :patch, :delete, :options, :head]

C/Rails 6 configuration

For rails 6 you will need to add the code below in your development.rb

//config/environments/development.rb config.hosts = nil


config.hosts << “your url”

IV Build Vertically

I build vertically, one object at a time for easy debugging and use “rails console” to test along the way.

A/ Build the Table and Migrate

Build tables

rails generate migration CreateHousesrails generate migration CreateGuests

The command will create a file with a timestamp. In this file, you can add your attributes and data type manually or generate synchronously with the table creation.

House table

//20201027175554_create_houses.rbclass CreateHouses < ActiveRecord::Migration[6.0]
def change
create_table :houses do |t|t.string :house_addresst.timestampsendendend

Guest table

//20201027182820_create_guests.rbclass CreateGuests < ActiveRecord::Migration[6.0]def changecreate_table :guests do |t|t.string :namet.string :phone_numbert.string :guest_addresst.string :emailt.string :time_linet.string :commentt.references :house, null:false, foreign_key: truet.timestampsendendend

I added a foreign key “house_id” to the guest table to be able to associate guests with a target house.


rake db:migrate

The command will create a new schema file. If you see a mistake in your table and column you can undo it by using “rake db: rollback”

//db/schema.rbActiveRecord::Schema.define(version: 2020_10_27_182820) docreate_table “guests”, force: :cascade do |t|t.string “name”t.string “phone_number”t.string “guest_address”t.string “email”t.string “time_line”t.string “comment”t.integer “house_id”, null: falset.datetime “created_at”, precision: 6, null: falset.datetime “updated_at”, precision: 6, null: falset.index [“house_id”], name: “index_guests_on_house_id”endcreate_table “houses”, force: :cascade do |t|t.string “house_address”t.datetime “created_at”, precision: 6, null: falset.datetime “updated_at”, precision: 6, null: falseendadd_foreign_key “guests”, “houses”end

B/Create seeds Data

rake db:seed

Create manually or use faker gem

//seeds.rbhouse = House.create(house_address: “testhouseadress”)guest = Guest.create(name: “nametest”, phone_number: “phonenumbertest”, guest_address: “guestsaddresstest”,email: “emailtest”,time_line:”timelinetest”, comment:”commenttest”, house_id:2)guest_two = Guest.create(name: “nametestwo”, phone_number: “phonenumbertestwo”, guest_address: “guestsaddresstesttwo”,email: “emailtesttwo”,time_line:”timelinetesttwo”, comment:”commenttesttwo”, house_id:3)

C/ Create models, controller, validations, routes

The model holds the relationship and validation, the controller holds the CRUD element create, read, update and delete both can be customized.

House Controller and model

//controllers/api/v1/houses_controller.rbclass Api::V1::HousesController < ApplicationControllerdef index
@houses = House.all
render json: @houses
def create
@house =
render json: @house
render json: {error: ‘Error creating house’}
def show
@house = House.find(params[:id])
render json: @house
def destroy
@house = House.find(params[:id])
privatedef house_params
//models/house.rbclass House < ApplicationRecordhas_many :guestsvalidates :house_address, presence: trueend

In the house model, each house is associate with multiple guests. The house_address attribute is required to create a house object.


To render different URL’s, you need routes. You can customize your route name. In this case, the URL will be localhost:3000/api/v1/houses. Verify your routes with “rails routes”. In this test case, because I created a nested object, I nested the routes as well.

Rails.application.routes.draw donamespace :api donamespace :v1 doresources :houses doresources :guestsendendendend

Guest controller and model

//app/models/guest.rbclass Guest < ApplicationRecord    
belongs_to :house
validates :name, :phone_number, :email, presence: true

The guest object belongs to the house object. A guest can't be created without a house_id. Also, the form will require the inputs of attributes to create a new guest object.

//controllers/api/v1/guests_controller.rbclass Api::V1::GuestsController < ApplicationControllerbefore_action :set_houserequire ‘pry’def index
@guests = Guest.all
render json: @guests
def show
@guest = Guest.find(params[:id])
render json: @guest
def create
@guest = @house.guests.create(guest_params)
render json: @house
render json: {error: ‘Error creating guest’}
def destroy
@guest = Guest.find(params[“id”])
@house = House.find(@guest.house_id)
render json: @house
privatedef set_house@house = House.find(params[:house_id])enddef guest_paramsparams.require(:guest).permit(:name, :phone_number, :guest_address, :email, :time_line, :comment, :house_id)endend

I created a private set_house method to require to have a house_id associate with a guest to create a guest and destroy a guest to avoid orphan data.


Rails S 

Voila! See JSON data at localhost3000/api/v1/houses




Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

WLST script to modify the JDBC data source targets in weblogic server


Getting started with RealityKit: Component Entity System

Documenting Ruby on Rails APIs Using rswag Gem

How to write bulletproof code in Go: a workflow for servers that can’t fail

Python Project on Kilometres Converter

How to Get Mailbox Database Size and White Space?

How To Create a Backup of your Instagram Profile Using Python

CS50x — Week 5

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Fadi Tillman

Fadi Tillman

More from Medium

Creating Rails Api Using Existing MySql Database

How to Create a New Ruby on Rails 7 App with PostgreSQL

Accessing a PostgreSQL database from a Ruby on Rails project with TablePlus

Add Sidekiq and Redis to Ruby on Rails Application with Capistrano