Web API Design
In the previous page, we retrieve and saved the records from a web api server
In this topic, we will design our own api similar with the PSGC
Go to app/controllers
directory, create the api
directory, this will be the home of all future API controllers.
Every API will eventually need to make an upgrade. We should presume that once our API is in use, someone is already using it. Avoid changing your APIs, except when errors need to be fixed.
We may plan for the future by using versions. Create a directory called v1
in the app/controller/api
directory;v1
is for version one and in this directory, we will add our current controllers. In the future, when we ever need to upgrade our API, we can generate a v2
directory.
Here are the endpoints that are we going to do.
/api/v1/regions
/api/v1/regions/:id
/api/v1/regions/:id/provinces/
/api/v1/provinces/
/api/v1/provinces/:id
/api/v1/provinces/:id/cities
/api/v1/cities/
/api/v1/cities/:id/
/api/v1/cities/:id/barangays/
/api/v1/barangays/
/api/v1/barangays/:id/
Regions
In the app/controllers/api/v1
, create the regions_controller.rb
file. add the index
and show
actions.
# app/controllers/api/v1/regions_controller.rb
class Api::V1::RegionsController < ApplicationController
def index
regions = Address::Region.all
render json: regions
end
def show
region = Address::Region.find(params[:id])
render json: region
end
end
In the controller, we will only need to have two actions in this controller. The actions retrieve all the region records from our database and return the data in JSON format.
Go to the config/routes.rb
directory. Add the resources inside the api
and v1
namespaces
# config/routes.rb
Rails.application.routes.draw do
# ---
+ namespace :api do
+ namespace :v1 do
+ resources :regions, only: %i[index show], defaults: { format: :json }
+ end
+ end
end
namespace
will prefix the URL path for the specified resources, and try to locate the controller under a module named in the same manner as the namespace.
Helper | HTTP Verb | Path | Controller#Action |
---|---|---|---|
api_v1_regions_path | GET | /api/v1/regions(.:format) | api/v1/regions#index {:format=>:json} |
api_v1_region_path | GET | /api/v1/regions/:id(.:format) | api/v1/regions#show {:format=>:json} |
Region Endpoints
http://localhost:3000/api/v1/regions
[
{
"id": 1,
"code": "010000000",
"name": "Region I",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 2,
"code": "020000000",
"name": "Region II",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
# ...
]
http://localhost:3000/api/v1/regions/1
{
"id": 1,
"code": "010000000",
"name": "Region I",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
}
Province
In the app/controllers/api/v1
, create the provinces_controller.rb
file. Similar with the region controller, we only need the index
and show
actions.
# app/controllers/api/v1/provinces_controller.rb
class Api::V1::ProvincesController < ApplicationController
def index
region = Address::Region.find_by_id(params[:region_id])
provinces = if region
region.provinces
else
Address::Province.all
end
render json: provinces
end
def show
province = Address::Province.find_by_id(params[:id])
render json: province
end
end
We determine whether a region is present and return all of its provinces in the index action. And if there isn’t a region, we simply return all the Philippine provinces. The show action uses the parameters id to return the specific province. Both actions return data in JSON format, exactly as the regions_controller does.
Go to the config/routes.rb
directory. and add those routes.
Rails.application.routes.draw do
# ---
namespace :api do
namespace :v1 do
- resources :regions, only: %i[index show], defaults: { format: :json }
+ resources :regions, only: %i[index show], defaults: { format: :json } do
+ resources :provinces, only: :index, defaults: { format: :json }
+ end
+
+ resources :provinces, only: %i[index show], defaults: { format: :json }
end
end
end
Helper | HTTP Verb | Path | Controller#Action |
---|---|---|---|
api_v1_region_provinces_path | GET | /api/v1/regions/:region_id/provinces(.:format) | api/v1/provinces#index {:format=>:json} |
api_v1_provinces_path | GET | /api/v1/provinces(.:format) | api/v1/provinces#index {:format=>:json} |
api_v1_province_path | GET | /api/v1/provinces/:id(.:format) | api/v1/provinces#show {:format=>:json} |
Province Endpoints
http://localhost:3000/api/v1/regions/1/provinces
[
{
"id": 1,
"region_id": 1,
"code": "012800000",
"name": "Ilocos Norte",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 2,
"region_id": 1,
"code": "012900000",
"name": "Ilocos Sur",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
#...
]
http://localhost:3000/api/v1/provinces
[
{
"id": 1,
"region_id": 1,
"code": "012800000",
"name": "Ilocos Norte",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 2,
"region_id": 1,
"code": "012900000",
"name": "Ilocos Sur",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
#...
]
http://localhost:3000/api/v1/provinces/1
{
"id": 1,
"region_id": 1,
"code": "012800000",
"name": "Ilocos Norte",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
}
City
In the app/controllers/api/v1
, create the cities_controller.rb
file.
# app/controllers/api/v1/cities_controller.rb
class Api::V1::CitiesController < ApplicationController
def index
province = Address::Province.find_by_id(params[:province_id])
cities = if province
province.cities
else
Address::City.all
end
render json: cities
end
def show
city = Address::City.find(params[:id])
render json: city
end
end
This is similar to the province controller. This controller checks whether a province is present before determining whether to return all cities or just the cities found within it.
Go to the config/routes.rb
directory.
Rails.application.routes.draw do
# ---
namespace :api do
namespace :v1 do
resources :regions, only: %i[index show], defaults: { format: :json } do
resources :provinces, only: :index, defaults: { format: :json }
end
- resources :provinces, only: %i[index show], defaults: { format: :json }
+ resources :provinces, only: %i[index show], defaults: { format: :json } do
+ resources :cities, only: :index, defaults: { format: :json }
+ end
+
+ resources :cities, only: %i[index show], defaults: { format: :json }
end
end
end
Helper | HTTP Verb | Path | Controller#Action |
---|---|---|---|
api_v1_province_cities_path | GET | /api/v1/provinces/:province_id/cities(.:format) | api/v1/cities#index {:format=>:json} |
api_v1_cities_path | GET | /api/v1/cities(.:format) | api/v1/cities#index {:format=>:json} |
api_v1_city_path | GET | /api/v1/cities/:id(.:format) | api/v1/cities#show {:format=>:json} |
City Endpoints
http://localhost:3000/api/v1/provinces/10/cities
[
{
"id": 219,
"province_id": 10,
"code": "030801000",
"name": "Abucay",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 220,
"province_id": 10,
"code": "030802000",
"name": "Bagac",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
]
http://localhost:3000/api/v1/cities/
[
{
"id": 1,
"province_id": 1,
"code": "012801000",
"name": "Adams",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 2,
"province_id": 1,
"code": "012802000",
"name": "Bacarra",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
]
http://localhost:3000/api/v1/cities/100
{
"id": 100,
"province_id": 4,
"code": "015523000",
"name": "Mabini",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
}
Barangay
The endpoints and lowest geographic hierarchy of the Philippines are covered in this final section. In the app/controllers/api/v1
, create the provinces_controller.rb
file.
# app/controllers/api/v1/barangays_controller.rb
class Api::V1::BarangaysController < ApplicationController
def index
city = Address::City.find_by_id(params[:city_id])
barangays = if city
city.barangays
else
Address::Barangay.all
end
render json: barangays
end
def show
barangay = Address::Barangay.find(params[:id])
render json: barangay
end
end
Go to the config/routes.rb
directory.
Rails.application.routes.draw do
# ...
namespace :api do
resources :regions, only: %i[index show], defaults: { format: :json } do
resources :provinces, only: :index, defaults: { format: :json }
end
resources :provinces, only: %i[index show], defaults: { format: :json } do
resources :cities, only: :index, defaults: { format: :json }
end
- resources :cities, only: %i[index show], defaults: { format: :json } do
+ resources :cities, only: %i[index show], defaults: { format: :json } do
+ resources :barangays, only: :index, defaults: { format: :json }
+ end
+
+ resources :barangays, only: %i[index show], defaults: { format: :json }
end
end
Helper | HTTP Verb | Path | Controller#Action |
---|---|---|---|
api_v1_city_barangays_path | GET | /api/v1/cities/:city_id/barangays(.:format) | api/v1/barangays#index {:format=>:json} |
api_v1_barangays_path | GET | /api/v1/barangays(.:format) | api/v1/barangays#index {:format=>:json} |
api_v1_barangay_path | GET | /api/v1/barangays/:id(.:format) | api/v1/barangays#show {:format=>:json} |
Barangay Endpoints
http://localhost:3000/api/v1/cities/100/barangays
[
{
"id": 2444,
"city_id": 100,
"code": "015523002",
"name": "Bacnit",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 2445,
"city_id": 100,
"code": "015523003",
"name": "Barlo",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
# ...
]
http://localhost:3000/api/v1/barangays
[
{
"id": 1,
"city_id": 1,
"code": "012801001",
"name": "Adams (Pob.)",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
{
"id": 2,
"city_id": 2,
"code": "012802001",
"name": "Bani",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
},
]
http://localhost:3000/api/v1/barangays/86
{
"id": 86,
"city_id": 4,
"code": "012804011",
"name": "Payac",
"created_at": "XXXX-XX-XXTXX:XX:XX.XXXZ",
"updated_at": "XXXX-XX-XXTXX:XX:XX.XXXZ"
}