Relaying Data through a Backend API

David Ryan Morphew
5 min readOct 26, 2021

--

Avoiding Unnecessary Database Storage

When you create a backend API, it might feel like second-nature to persist any data you get from a 3rd party API that you want to use in your own app, and display on your frontend.

External API ---> Your API ---> Your Frontend

Take this really simple app I’m working on, where I fetch Mars Rover images to display through my React frontend:

Insta-Space display of Mars Rover Images

For this, I fetch Mars Rover images from a NASA API through my own backend API (created with Ruby on Rails).

Why Relay Through Your Own API?

  1. Why use my own API? Well, it turns out that using an API Key securely with a React frontend might be a bit trickier than just using your own API to keep your keys secure.
  2. Also, I want to persist the data of NASA images if, and only if, you interact with the images, such as when you click the heart to show that you like a particular image, or you add a comment to the image.
  3. Further, and this is a BIG one:

There’s a scalability issue you run into if you save ALL of the images, or data that you gather from an external API.

  • I don’t really have room to save every image I get from the NASA API, especially if I actually want to use this app frequently or for many users.
  • So, I thought,

“I’ll make my API a ‘relay’ for the images until someone interacts with one, and in that case, I’ll persist it to my database.”

Passing the baton in a relay race.
Photo by Magic Keegan on Unsplash

Planning Your Relay Flow

This requires planning for two things:

  1. Checking that the information isn’t already saved to your database. (Sometimes you do want to save the data, after all.) If it is saved, retrieve the persisted information.
  2. Formatting the data you are relaying so that it can easily be persisted in your database when you want to save it, and displayed on your frontend, just like any other information you’ve already saved.

Here’s the API class with the fetch_images method, which fetches Mars Rover Images from the NASA API, and the format_data method, which formats the relayed data to look like.

class Api < ApplicationRecord
@@base_url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/'
@@rover_array = ["curiosity", "spirit", "opportunity"]
@@rover_max_sol = {"curiosity" => 3241, "spirit" => 2208,
"opportunity" => 5111}
@@camera_names = {
"FHAZ" => "Front Hazard Avoidance Camera",
"RHAZ" => "Rear Hazard Avoidance Camera",
"MAST" => "Mast Camera",
"CHEMCAM" => "Chemistry and Camera Complex",
"MAHLI" => "Mars Hand Lens Imager",
"MARDI" => "Mars Descent Imager",
"NAVCAM" => "Navigation Camera",
"PANCAM" => "Panoramic Camera",
"MINITES" => "Miniature Thermal Emission Spectrometer"
}
def self.fetch_images(earth_date = nil)
# randomize rover
rover = @@rover_array[rand(2)]
max_sol = @@rover_max_sol[rover]
if earth_date
date_query = "earth_date=#{earth_date}"
else
date_query = "sol=#{rand(max_sol)}"
end
# Set data parameters to actual dates for opportunity rover in
earth_date
url = "#{@@base_url}#{rover}/photos?#{date_query}&api_key=#
{ENV["NASA_API_KEY"]}"
uri = URI(url)
resp = Net::HTTP.get(uri)
photos = JSON.parse(resp)
data = photos["photos"]
# get more data if insufficient for frontend display
data = self.format_data(data, rover).compact
if data.count < 15
data += self.fetch_images
end
data.shuffle
end
def self.format_data(data, rover)
data.map do |image_data|
if image_data["camera"] && image_data["earth_date"]
camera = @@camera_names[image_data["camera"]["name"]]
title = "#{rover.titleize} Rover—#{camera}—#{image_data["id"]}"
if saved_image = Image.find_by(title: title)
ImageSerializer.new(saved_image)
else
date = image_data["earth_date"]
{
image_url: image_data["img_src"],
title: title,
date_of_capture: date,
like_count: 0,
comment_count: 0
}
end
end
end
end
end

There are some extra things up there ^^ that are used to randomize which rover and which day is selected for images, as well as decoding the API data for the type of camera on a rover that took a particular image.

But, for the purposes of the relay example at hand, the important thing concerns the formatting of the data.

Data Formatting

if saved_image = Image.find_by(title: title)
ImageSerializer.new(saved_image)
else
date = image_data["earth_date"]
{
image_url: image_data["img_src"],
title: title,
date_of_capture: date,
like_count: 0,
comment_count: 0
}
end

When Found in Our Database

If the image is already saved in the database, we can find it by its unique title, which is a combination of the rover name, the camera used to take the shot, and the image id stored in the NASA database:

title = “#{rover.titleize} Rover — #{camera} — #{image_data[“id”]}”

The ImageSerializer formats the data that is passed as a JSON object to our frontend like this:

class ImageSerializer < ActiveModel::Serializer
attributes :id, :image_url, :title, :date_of_capture, :like_count,
:comment_count
end

When Relayed and Not Saved

If the image is not already saved in the database, we take each of the pieces of information we want to use, and which we might want to save in our database, and we return it in a hash structure that is identical to the hash-like JSON object structure we returned if the entry for this image already exists in our database:

date = image_data["earth_date"]
{
image_url: image_data["img_src"],
title: title,
date_of_capture: date,
like_count: 0,
comment_count: 0
}

Note that the attributes here match those found for the saved image hash we returned above (and converted to a JSON object), with the exception of an “id,” since that only belongs to image data that has been persisted in our database, and is assigned at the creation of an image data’s entry into our database. So, we should not add an id when relaying an image that is not saved.

There certainly are other ways to do this, but this method seemed like a pretty straightforward way around saving every bit of information I gathered from the API and displayed to the user on through the frontend.

--

--

David Ryan Morphew

I’m very excited to start a new career in Software Engineering. I love the languages, frameworks, and libraries I’ve already learned / worked with (Ruby, Rails,