Skip to content

Endpoints Basic API

sergio edited this page Oct 25, 2020 · 24 revisions

We can use multiple routes to differentiate between specific requests. In an API, these are typically referred to as endpoints. A user of the API uses endpoints to access specific pieces of data. Just like a normal Rails app, we can create full CRUD based controllers that only render JSON.

Rails.application.routes.draw do
  get '/birds' => 'birds#index'
  get '/birds/:id' => 'birds#show' # new
end
class BirdsController < ApplicationController
  def index
    birds = Bird.all
    render json: birds
  end

  def show
    bird = Bird.find_by(id: params[:id])
    render json: bird
  end
end

An API that sends user information might contain details of a user internally that it does not want to ever share externally.

{
  "id": 2,
  "name": "Grackle",
  "species": "Quiscalus Quiscula",
  "created_at": "2019-05-09T21:51:41.543Z",
  "updated_at": "2019-05-09T21:51:41.543Z"
}

We probably don't need bits of data like created_at and updated_at. Rather than send this unnecessary info when rendering, we could just pick and choose what we want to send:

def show
  bird = Bird.find_by(id: params[:id])
  render json: {id: bird.id, name: bird.name, species: bird.species } 
end

.slice() simplifies it:

def show
  bird = Bird.find_by(id: params[:id])
  render json: bird.slice(:id, :name, :species)
end

both methods above results in:

{
  "id": "3",
  "name": "Common Starling",
  "species": "Sturnus Vulgaris"
}

what if we have collection of hashes?

def index
  birds = Bird.all
  render json: birds
end

only:

def index
  birds = Bird.all
  render json: birds, only: [:id, :name, :species]
end

except:

def index
  birds = Bird.all
  render json: birds, except: [:created_at, :updated_at]
end

Error Messages

def show
  bird = Bird.find_by(id: params[:id])
  if bird
    render json: { id: bird.id, name: bird.name, species: bird.species }
  else
    render json: { message: 'Bird not found' }
  end
end

Now, if we were to send a request to an invalid endpoint like http://localhost:3000/birds/hello_birds, rather than receiving a general HTTP error, we would still receive a response from the API:

{
  "message": "Bird not found"
}

Using include

In this lesson, we will look at how our Rails API will be able to convey relationships between multiple models in a single JSON object.

def show
  sighting = Sighting.find_by(id: params[:id])
  render json: sighting
end

visiting http://localhost:3000/sightings/1:

{
  "id": 1,
  "bird_id": 1,
  "location_id": 2,
  "created_at": "2019-05-14T11:20:37.225Z",
  "updated_at": "2019-05-14T11:20:37.225Z"
}

NOTE: Notice that the object includes its own "id", as well as the related "bird_id" and "location_id". That is useful data. We could use these values to send additional requests using JavaScript to get bird and location data if needed.

def show
  sighting = Sighting.find_by(id: params[:id])
  render json: { id: sighting.id, bird: sighting.bird, location: sighting.location }
end
{
  "id": 2,
  "bird": {
    "id": 2,
    "name": "Grackle",
    "species": "Quiscalus Quiscula",
    "created_at": "2019-05-14T11:20:37.177Z",
    "updated_at": "2019-05-14T11:20:37.177Z"
  },
  "location": {
    "id": 2,
    "latitude": 30.26715,
    "longitude": -97.74306,
    "created_at": "2019-05-14T11:20:37.196Z",
    "updated_at": "2019-05-14T11:20:37.196Z"
  }
}

An alternative is to use the include option to indicate what models you want to nest:

def show
  sighting = Sighting.find_by(id: params[:id])
  render json: sighting, include: [:bird, :location]
end

 #also works fine when dealing with an action that renders an array
def index
  sightings = Sighting.all
  render json: sightings, include: [:bird, :location]
end
{
  "id": 2,
  "bird_id": 2,
  "location_id": 2,
  "created_at": "2019-05-14T11:20:37.228Z",
  "updated_at": "2019-05-14T11:20:37.228Z",
  "bird": {
    "id": 2,
    "name": "Grackle",
    "species": "Quiscalus Quiscula",
    "created_at": "2019-05-14T11:20:37.177Z",
    "updated_at": "2019-05-14T11:20:37.177Z"
  },
  "location": {
    "id": 2,
    "latitude": 30.26715,
    "longitude": -97.74306,
    "created_at": "2019-05-14T11:20:37.196Z",
    "updated_at": "2019-05-14T11:20:37.196Z"
  }
}

To get more control of the data shown also in the nested Objects:

def show
  sighting = Sighting.find_by(id: params[:id])
  render json: sighting, include: {
    bird: {only: [:name, :species]},
    location: {only: [:latitude, :longitude]}
  }, except: [:updated_at]
end
{
  "id": 2,
  "bird_id": 2,
  "location_id": 2,
  "created_at": "2019-05-14T11:20:37.228Z",
  "bird": {
    "name": "Grackle",
    "species": "Quiscalus Quiscula"
  },
  "location": {
    "latitude": 30.26715,
    "longitude": -97.74306
  }
}


We can now take a single model or all the instances of that model and render it to JSON, extracting out any specific content we do or do not want to send!

Whether you are building a professional API for a company or for your own personal site, having the ability to fine tune how your data look is a critical skill that we're only just beginning to scratch the surface on.

Clone this wiki locally