Overview
Ruby provides multiple approaches for working with REST APIs, from the built-in Net::HTTP library to third-party gems like HTTParty and Faraday. REST (Representational State Transfer) APIs use HTTP methods (GET, POST, PUT, DELETE) to perform operations on resources identified by URLs.
Ruby's standard library includes Net::HTTP for basic HTTP operations, while JSON and URI libraries handle data serialization and URL manipulation. The ecosystem offers gems that simplify REST API interactions with features like automatic JSON parsing, middleware support, and connection pooling.
require 'net/http'
require 'json'
uri = URI('https://api.example.com/users')
response = Net::HTTP.get_response(uri)
data = JSON.parse(response.body) if response.code == '200'
Ruby applications commonly consume REST APIs for external service integration, data synchronization, and microservice communication. The language's expressiveness and gem ecosystem make it suitable for both API consumption and creation.
# Using HTTParty gem
require 'httparty'
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
def get_user(id)
self.class.get("/users/#{id}")
end
end
Basic Usage
Net::HTTP forms Ruby's foundation for HTTP communication. The library requires explicit connection management and manual request construction, providing low-level control over HTTP operations.
require 'net/http'
require 'json'
require 'uri'
# GET request
uri = URI('https://jsonplaceholder.typicode.com/posts/1')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Accept'] = 'application/json'
response = http.request(request)
puts JSON.parse(response.body)['title']
POST requests require body content and appropriate headers. Net::HTTP handles various content types through manual header configuration.
# POST request with JSON body
uri = URI('https://jsonplaceholder.typicode.com/posts')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request.body = JSON.generate({
title: 'New Post',
body: 'Post content',
userId: 1
})
response = http.request(request)
created_post = JSON.parse(response.body)
HTTParty simplifies REST API consumption by providing a DSL for common operations. The gem automatically handles JSON parsing and provides convenient methods for HTTP verbs.
require 'httparty'
class BlogApi
include HTTParty
base_uri 'https://jsonplaceholder.typicode.com'
def self.get_posts
get('/posts')
end
def self.create_post(title, body, user_id)
post('/posts', {
body: {
title: title,
body: body,
userId: user_id
}.to_json,
headers: { 'Content-Type' => 'application/json' }
})
end
end
posts = BlogApi.get_posts
new_post = BlogApi.create_post('Title', 'Content', 1)
Faraday offers middleware-based HTTP communication with pluggable adapters. The gem supports request/response modification through middleware stacks.
require 'faraday'
require 'faraday/net_http'
conn = Faraday.new(
url: 'https://api.example.com',
headers: { 'Content-Type' => 'application/json' }
) do |f|
f.request :json
f.response :json
f.adapter :net_http
end
response = conn.get('/users/1')
user_data = response.body
new_user = conn.post('/users') do |req|
req.body = { name: 'John', email: 'john@example.com' }
end
Error Handling & Debugging
REST API communication involves multiple failure modes: network timeouts, HTTP errors, malformed JSON, and service unavailability. Ruby provides exception handling mechanisms for each category.
Net::HTTP raises different exception types based on failure conditions. Network-level failures raise socket exceptions, while HTTP-level issues require response code inspection.
require 'net/http'
require 'timeout'
require 'json'
def safe_api_request(uri_string)
uri = URI(uri_string)
begin
response = Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == 'https',
open_timeout: 5,
read_timeout: 10) do |http|
request = Net::HTTP::Get.new(uri)
http.request(request)
end
case response.code
when '200'
JSON.parse(response.body)
when '404'
raise "Resource not found: #{uri}"
when '429'
raise "Rate limited. Retry after #{response['Retry-After']} seconds"
when /^5/
raise "Server error: #{response.code} #{response.message}"
else
raise "Unexpected response: #{response.code}"
end
rescue Timeout::Error
raise "Request timeout for #{uri}"
rescue Errno::ECONNREFUSED
raise "Connection refused to #{uri.host}:#{uri.port}"
rescue JSON::ParserError => e
raise "Invalid JSON response: #{e.message}"
rescue SocketError => e
raise "Network error: #{e.message}"
end
end
HTTParty provides response objects with built-in success checking and exception handling options. The gem can raise exceptions on HTTP errors or return response objects for manual inspection.
require 'httparty'
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
timeout 10
def self.get_user_safe(id)
response = get("/users/#{id}")
if response.success?
response.parsed_response
else
handle_error(response)
end
rescue Net::TimeoutError
{ error: 'Request timeout' }
rescue HTTParty::Error => e
{ error: "HTTP error: #{e.message}" }
end
private
def self.handle_error(response)
case response.code
when 401
{ error: 'Authentication required' }
when 403
{ error: 'Access forbidden' }
when 404
{ error: 'User not found' }
when 422
{ error: 'Validation failed', details: response.parsed_response }
when 500..599
{ error: 'Server error', code: response.code }
else
{ error: 'Unknown error', code: response.code }
end
end
end
Implementing retry logic handles transient failures common in distributed systems. Exponential backoff prevents overwhelming failing services.
class RetryableApiClient
MAX_RETRIES = 3
BASE_DELAY = 1
def self.request_with_retry(uri, max_retries = MAX_RETRIES)
retries = 0
begin
yield
rescue Net::TimeoutError, Errno::ECONNREFUSED, Net::HTTPServerError => e
retries += 1
if retries <= max_retries
delay = BASE_DELAY * (2 ** (retries - 1))
sleep(delay + rand(0.1)) # Add jitter
retry
else
raise "Failed after #{max_retries} retries: #{e.message}"
end
end
end
end
Production Patterns
Production REST API usage requires connection pooling, monitoring, authentication handling, and configuration management. Ruby applications typically centralize API configuration and provide consistent error handling across service boundaries.
Connection pooling reduces overhead for high-throughput applications. Net::HTTP::Persistent maintains persistent connections across requests.
require 'net/http/persistent'
require 'json'
class ProductionApiClient
def initialize(base_url)
@base_url = base_url
@http = Net::HTTP::Persistent.new(name: 'api_client')
@http.idle_timeout = 30
@http.retry_change_requests = true
end
def get(path, headers = {})
uri = URI.join(@base_url, path)
request = Net::HTTP::Get.new(uri)
headers.each { |key, value| request[key] = value }
request['User-Agent'] = "MyApp/1.0"
response = @http.request(uri, request)
log_request(request.method, uri, response.code)
case response.code
when '200'
JSON.parse(response.body)
else
raise ApiError.new(response.code, response.body)
end
rescue JSON::ParserError => e
raise ApiError.new('parse_error', e.message)
end
def shutdown
@http.shutdown
end
private
def log_request(method, uri, status)
Rails.logger.info "API #{method} #{uri} #{status}" if defined?(Rails)
end
end
class ApiError < StandardError
attr_reader :code, :details
def initialize(code, details)
@code = code
@details = details
super("API Error #{code}: #{details}")
end
end
Authentication patterns vary by API provider. Token-based authentication requires header management and token refresh logic.
class AuthenticatedApiClient
def initialize(api_key, secret = nil)
@api_key = api_key
@secret = secret
@access_token = nil
@token_expires_at = nil
end
def authenticated_request(method, path, body = nil)
ensure_valid_token
headers = {
'Authorization' => "Bearer #{@access_token}",
'Content-Type' => 'application/json'
}
case method.upcase
when 'GET'
HTTParty.get(build_url(path), headers: headers)
when 'POST'
HTTParty.post(build_url(path), headers: headers, body: body.to_json)
when 'PUT'
HTTParty.put(build_url(path), headers: headers, body: body.to_json)
when 'DELETE'
HTTParty.delete(build_url(path), headers: headers)
end
end
private
def ensure_valid_token
return if @access_token && @token_expires_at > Time.now
auth_response = HTTParty.post(
build_url('/oauth/token'),
body: {
grant_type: 'client_credentials',
client_id: @api_key,
client_secret: @secret
}
)
if auth_response.success?
@access_token = auth_response['access_token']
@token_expires_at = Time.now + auth_response['expires_in'].to_i
else
raise "Authentication failed: #{auth_response.code}"
end
end
def build_url(path)
"https://api.example.com#{path}"
end
end
Configuration management centralizes API settings and supports environment-specific values. Rails applications typically use environment variables and configuration classes.
class ApiConfiguration
class << self
def base_url
ENV.fetch('API_BASE_URL', 'https://api.example.com')
end
def api_key
ENV.fetch('API_KEY') { raise 'API_KEY required' }
end
def timeout
ENV.fetch('API_TIMEOUT', '30').to_i
end
def max_retries
ENV.fetch('API_MAX_RETRIES', '3').to_i
end
def rate_limit_per_minute
ENV.fetch('API_RATE_LIMIT', '60').to_i
end
end
end
class RateLimitedApiClient
def initialize
@requests_this_minute = 0
@minute_started_at = Time.now
end
def request(method, path, body = nil)
enforce_rate_limit
# Make request using configured values
HTTParty.send(
method.downcase,
"#{ApiConfiguration.base_url}#{path}",
headers: { 'Authorization' => "Bearer #{ApiConfiguration.api_key}" },
body: body&.to_json,
timeout: ApiConfiguration.timeout
)
end
private
def enforce_rate_limit
current_time = Time.now
if current_time - @minute_started_at >= 60
@requests_this_minute = 0
@minute_started_at = current_time
end
if @requests_this_minute >= ApiConfiguration.rate_limit_per_minute
sleep_time = 60 - (current_time - @minute_started_at)
sleep(sleep_time) if sleep_time > 0
@requests_this_minute = 0
@minute_started_at = Time.now
end
@requests_this_minute += 1
end
end
Reference
HTTP Client Libraries
Library | Installation | Primary Use Case | Key Features |
---|---|---|---|
Net::HTTP |
Built-in | Low-level HTTP control | Manual connection management, SSL support |
HTTParty |
gem install httparty |
Simple REST API consumption | DSL, automatic JSON parsing, class-based |
Faraday |
gem install faraday |
Middleware-based HTTP | Pluggable adapters, middleware stack |
Rest-Client |
gem install rest-client |
Quick REST operations | Simple method-based API, built-in JSON |
Net::HTTP Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Net::HTTP.get(uri) |
uri (URI/String) |
String |
Simple GET request |
Net::HTTP.get_response(uri) |
uri (URI/String) |
Net::HTTPResponse |
GET with response object |
Net::HTTP.post(uri, data) |
uri (URI/String), data (String) |
String |
Simple POST request |
Net::HTTP.start(host, port, opts) |
host (String), port (Integer), options (Hash) |
Connection block | Persistent connection |
HTTParty Methods
Method | Parameters | Returns | Description |
---|---|---|---|
get(path, opts) |
path (String), options (Hash) |
HTTParty::Response |
GET request |
post(path, opts) |
path (String), options (Hash) |
HTTParty::Response |
POST request |
put(path, opts) |
path (String), options (Hash) |
HTTParty::Response |
PUT request |
delete(path, opts) |
path (String), options (Hash) |
HTTParty::Response |
DELETE request |
base_uri(uri) |
uri (String) |
String |
Set base URL |
headers(hash) |
hash (Hash) |
Hash |
Set default headers |
Common HTTP Status Codes
Code | Meaning | Typical Handling |
---|---|---|
200 |
OK | Parse response body |
201 |
Created | Extract created resource |
400 |
Bad Request | Check request parameters |
401 |
Unauthorized | Refresh authentication |
403 |
Forbidden | Check permissions |
404 |
Not Found | Handle missing resource |
422 |
Unprocessable Entity | Display validation errors |
429 |
Too Many Requests | Implement rate limiting |
500 |
Internal Server Error | Retry with backoff |
JSON Processing
Method | Purpose | Example |
---|---|---|
JSON.parse(string) |
Parse JSON string | JSON.parse('{"key":"value"}') |
JSON.generate(object) |
Create JSON string | JSON.generate({key: "value"}) |
to_json |
Object to JSON | {key: "value"}.to_json |
Error Classes
Exception | Cause | Handling Strategy |
---|---|---|
Timeout::Error |
Request timeout | Retry with backoff |
Errno::ECONNREFUSED |
Connection refused | Check service availability |
SocketError |
DNS/network issue | Verify host and connectivity |
Net::HTTPError |
HTTP protocol error | Inspect response code |
JSON::ParserError |
Invalid JSON | Validate response format |
URI::InvalidURIError |
Malformed URL | Validate URL construction |
Configuration Options
Option | Net::HTTP | HTTParty | Faraday | Purpose |
---|---|---|---|---|
Timeout | open_timeout , read_timeout |
timeout |
request: { timeout: n } |
Request timeout |
SSL | use_ssl = true |
base_uri 'https://' |
ssl: { verify: true } |
HTTPS configuration |
Headers | request['Header'] = 'value' |
headers class method |
headers: {} |
Default headers |
User Agent | Manual header setting | headers 'User-Agent' => 'App' |
headers: { 'User-Agent' => 'App' } |
Client identification |