Google Sheets as a REST API in Ruby
Fetch and query Google Sheet data in Ruby using SheetsAPI with net/http, typed structs, Faraday, and Rails integration examples.
3 min read
Ruby's concise syntax and net/http standard library make it easy to consume REST APIs
with no external dependencies. SheetsAPI returns plain JSON, so you can be up and running
in minutes whether you are writing a standalone script or a Rails app.
Prerequisites
- Ruby 3.0+
- A Google Sheet with a header row
- A SheetsAPI account and a
YOUR_USER_KEYkey from the dashboard
1. Basic fetch with net/http
require "net/http"
require "uri"
require "json"
BASE_URL = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets"
def fetch_sheet(user_key, sheet_name, params = {})
uri = URI("#{BASE_URL}/#{user_key}/#{sheet_name}")
uri.query = URI.encode_www_form(params) unless params.empty?
response = Net::HTTP.get_response(uri)
raise "API error #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body, symbolize_names: true)
end
result = fetch_sheet("YOUR_USER_KEY", "Products", limit: 20, sort: "name")
puts "Total: #{result[:meta][:total]}"
result[:data].each { |row| puts "#{row[:name]} - #{row[:price]}" }Replace YOUR_USER_KEY with your actual key.
2. Reusable client class
require "net/http"
require "uri"
require "json"
class SheetsApiClient
BASE = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets"
def initialize(user_key, api_key: nil)
@user_key = user_key
@api_key = api_key
end
def query(sheet_name, limit: 20, offset: 0, search: nil, sort: nil, fields: nil)
uri = URI("#{BASE}/#{@user_key}/#{sheet_name}")
params = { limit:, offset: }
params[:search] = search if search
params[:sort] = sort if sort
params[:fields] = fields if fields
uri.query = URI.encode_www_form(params)
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{@api_key}" if @api_key
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 5
http.read_timeout = 10
response = http.request(request)
raise "SheetsAPI error: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body, symbolize_names: true)
end
def all(sheet_name, page_size: 100)
rows = []
offset = 0
loop do
result = query(sheet_name, limit: page_size, offset:)
rows.concat(result[:data])
offset += page_size
break if offset >= result[:meta][:total]
end
rows
end
endUsage:
client = SheetsApiClient.new("YOUR_USER_KEY", api_key: "sk_your_api_key_here")
# Search for electronics under a certain category
electronics = client.query("Products",
limit: 50,
search: "category:electronics",
sort: "-price"
)
# Fetch every row (auto-paginated)
all_products = client.all("Products")
puts "#{all_products.size} total products"3. Using Faraday (popular HTTP client)
# Gemfile: gem "faraday"
require "faraday"
require "json"
conn = Faraday.new(url: "https://sheetsapi.gkit.mreshank.com") do |f|
f.request :url_encoded
f.response :raise_error
f.headers["Authorization"] = "Bearer sk_your_api_key_here"
end
response = conn.get("/api/spreadsheets/YOUR_USER_KEY/Products") do |req|
req.params["limit"] = 20
req.params["sort"] = "name"
end
data = JSON.parse(response.body, symbolize_names: true)
data[:data].each { |p| puts p[:name] }4. Rails: service object pattern
# app/services/sheets_api_service.rb
class SheetsApiService
BASE_URL = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets"
def initialize
@user_key = ENV.fetch("SHEETS_API_USER_KEY")
@api_key = ENV.fetch("SHEETS_API_KEY", nil)
end
def query(sheet_name, **opts)
uri = URI("#{BASE_URL}/#{@user_key}/#{sheet_name}")
params = opts.compact
uri.query = URI.encode_www_form(params) unless params.empty?
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{@api_key}" if @api_key
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
JSON.parse(http.request(req).body, symbolize_names: true)
end
end# app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
service = SheetsApiService.new
result = service.query("Products",
limit: params.fetch(:limit, 20).to_i,
search: params[:q].present? ? "name:#{params[:q]}" : nil,
sort: params.fetch(:sort, "name")
)
@products = result[:data]
@total = result[:meta][:total]
end
endSet environment variables in config/credentials.yml.enc or .env:
SHEETS_API_USER_KEY=YOUR_USER_KEY
SHEETS_API_KEY=sk_your_api_key_here
5. Caching with Rails.cache
def cached_products(search: nil)
cache_key = "products/#{search || "all"}"
Rails.cache.fetch(cache_key, expires_in: 5.minutes) do
SheetsApiService.new.query("Products",
limit: 200,
search: search ? "name:#{search}" : nil
)[:data]
end
end6. Query parameter reference
| Parameter | Example | Description |
|---|---|---|
limit | 50 | Rows per page (default: 20, max: 500) |
offset | 0 | Row offset for pagination |
search | category:books | Filter by field:value |
sort | price or -price | Ascending / descending |
fields | name,price | Return only named columns |
Summary
| Concern | Approach |
|---|---|
| HTTP | net/http (stdlib) or Faraday |
| Types | symbolize_names: true hashes |
| Auth | Authorization: Bearer header |
| Pagination | offset loop until offset >= meta[:total] |
| Rails | Service object + Rails.cache |
No gems required for the basics - SheetsAPI speaks plain JSON and Ruby's standard library handles the rest.