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]
end
end
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
or
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 changecreate_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.
Migrate
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
enddef create
@house = House.new(house_params)
if @house.save
render json: @house
else
render json: {error: ‘Error creating house’}
end
enddef show
@house = House.find(params[:id])
render json: @house
end
def destroy
@house = House.find(params[:id])
@house.destroy
endprivatedef house_params
params.require(:house).permit(:house_address)
end
end//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.
ROUTES
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
end
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
enddef show
@guest = Guest.find(params[:id])
render json: @guest
enddef create
@guest = @house.guests.create(guest_params)
if @guest.save
render json: @house
else
render json: {error: ‘Error creating guest’}
end
enddef destroy
@guest = Guest.find(params[“id”])
@house = House.find(@guest.house_id)
@guest.destroy
render json: @house
endprivatedef 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.
Result
Rails S
Voila! See JSON data at localhost3000/api/v1/houses