RoR Consume External APIs
Your Rails app can call other services' APIs to fetch data, send messages, process payments, and more. Consuming an external API means your app sends an HTTP request to another server and processes the response. Ruby has built-in and third-party tools that make this easy.
How API Consumption Works
Your Rails App External API Server
| |
| GET https://api.weather.com/ |
| /current?city=London |
| ─────────────────────────────► |
| |
| Returns JSON: |
| { "temp": 18, "desc": "Cloudy" }|
| ◄────────────────────────────── |
| |
Your app reads the JSON
and uses the data
The Net::HTTP Library (Built-in Ruby)
Ruby includes Net::HTTP with no gem required:
require "net/http"
require "json"
uri = URI("https://api.exchangerate.host/latest?base=USD")
response = Net::HTTP.get(uri)
data = JSON.parse(response)
data["rates"]["EUR"] # => 0.93
data["rates"]["GBP"] # => 0.79
The HTTParty Gem (Recommended)
HTTParty makes HTTP requests cleaner with less boilerplate:
gem "httparty" bundle install
require "httparty"
response = HTTParty.get(
"https://api.openweathermap.org/data/2.5/weather",
query: {
q: "London",
appid: ENV["OPENWEATHER_API_KEY"],
units: "metric"
}
)
if response.success?
data = response.parsed_response
temp = data["main"]["temp"] # 18.4
desc = data["weather"][0]["desc"] # "light rain"
else
Rails.logger.error "Weather API failed: #{response.code}"
end
Wrap External APIs in a Service Object
Never call external APIs directly from controllers. Wrap them in a service object so the logic stays organized and testable:
app/services/weather_service.rb
class WeatherService
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
def initialize(city)
@city = city
end
def current
response = HTTParty.get(BASE_URL, query: query_params)
return nil unless response.success?
parse_weather(response.parsed_response)
end
private
def query_params
{
q: @city,
appid: ENV["OPENWEATHER_API_KEY"],
units: "metric"
}
end
def parse_weather(data)
{
city: data["name"],
temperature: data["main"]["temp"],
feels_like: data["main"]["feels_like"],
description: data["weather"][0]["description"],
humidity: data["main"]["humidity"]
}
end
end
Use it in a controller:
class WeatherController < ApplicationController
def show
@weather = WeatherService.new(params[:city]).current
if @weather.nil?
flash[:alert] = "Could not fetch weather data."
redirect_to root_path
end
end
end
Handling Authentication with External APIs
Most APIs require authentication. Common methods:
API Key in query string:
HTTParty.get(url, query: { api_key: ENV["API_KEY"] })
API Key in header:
HTTParty.get(url, headers: { "X-API-Key" => ENV["API_KEY"] })
Bearer Token:
HTTParty.get(url, headers: { "Authorization" => "Bearer #{ENV["TOKEN"]}" })
Basic Auth:
HTTParty.get(url, basic_auth: { username: "user", password: "pass" })
POST Requests to External APIs
Send JSON data to an external API:
response = HTTParty.post(
"https://api.sendgrid.com/v3/mail/send",
headers: {
"Authorization" => "Bearer #{ENV["SENDGRID_API_KEY"]}",
"Content-Type" => "application/json"
},
body: {
personalizations: [{ to: [{ email: "user@example.com" }] }],
from: { email: "noreply@myapp.com" },
subject: "Welcome!",
content: [{ type: "text/plain", value: "Thanks for signing up." }]
}.to_json
)
response.code # 202 = accepted
Caching API Responses
External API calls are slow and often rate-limited. Cache responses to avoid redundant calls:
def current_weather(city)
Rails.cache.fetch("weather_#{city}", expires_in: 30.minutes) do
WeatherService.new(city).current
end
end
The first call hits the API. Subsequent calls within 30 minutes return the cached result instantly.
Error Handling
def fetch_data(url)
response = HTTParty.get(url, timeout: 5)
case response.code
when 200 then response.parsed_response
when 401 then raise "API authentication failed"
when 404 then nil
when 429 then raise "Rate limit exceeded — try again later"
when 500..599 then raise "External API server error"
end
rescue Net::OpenTimeout, Net::ReadTimeout
Rails.logger.error "API request timed out: #{url}"
nil
rescue HTTParty::Error => e
Rails.logger.error "HTTParty error: #{e.message}"
nil
end
Faraday — A Flexible HTTP Client
For apps that make many API calls, Faraday offers middleware, retries, and connection pooling:
gem "faraday"
bundle install
conn = Faraday.new(url: "https://api.github.com") do |f|
f.request :json
f.response :json
f.adapter Faraday.default_adapter
f.headers["Authorization"] = "Bearer #{ENV["GITHUB_TOKEN"]}"
end
response = conn.get("/repos/rails/rails")
response.body["stargazers_count"] # => GitHub stars count
Common External APIs Used with Rails
| Service | Purpose | Gem Available |
|---|---|---|
| Stripe | Payment processing | stripe |
| Twilio | SMS and phone calls | twilio-ruby |
| SendGrid | Transactional email | sendgrid-ruby |
| Google Maps | Geocoding and maps | geocoder |
| OpenAI | AI text generation | ruby-openai |
| GitHub | Repository data | octokit |
Always store API keys in environment variables or Rails credentials. Never commit them to version control. Wrap API calls in service objects, handle timeouts and errors gracefully, and cache responses where possible to keep your app fast and reliable.
