GR API Manager
A minimal, opinionated Ruby wrapper around Sinatra that eliminates boilerplate from REST API development. Authentication, parameter validation, type casting, CORS, and error handling are handled at the framework level — you write only the business logic.
Features
- Available on RubyGems! Install globally and use it in any project.
- Route-level Bearer Token authentication (opt-in/opt-out per endpoint).
- Declarative required parameter validation with automatic
400 Bad Requestresponses. - Smart type casting for query string values (
"10"->Integer,"true"->TrueClass,"9.5"->Float). - Global JSON error responses for
404and500. - Broad CORS and
OPTIONSpreflight handling out of the box (Frontend ready). - API versioning via configurable route prefix (e.g.
/api/v1).
Installation
Add this line to your application's Gemfile:
gem 'gr-api-manager'
And then execute:
bundle install
Or install it yourself directly via RubyGems:
gem install gr-api-manager
Quick Start
require 'gr_api_manager'
api = GRApiManager::Server.new(
port: 4567,
bearer_token: "your_secret_token",
prefix: "/api/v1"
)
# Public endpoint
api.get('/health', auth: false) do
{ status: 'online', time: Time.now.to_s }
end
api.run!
curl http://localhost:4567/api/v1/health
# => {"status":"online","time":"..."}
Configuration
All parameters are optional. If omitted, the manager falls back to environment variables loaded from a .env file at the project root (via dotenv). If neither is provided, defaults are used.
GRApiManager::Server.new(
port: 4567, # Default: ENV['PORT'] || 4000
bearer_token: "secret", # Default: ENV['API_TOKEN']
permitted_hosts: ["example.com"], # Host allowlist — empty means allow all
prefix: "/api/v1" # Route prefix applied to all endpoints
)
Using a .env file (Recommended)
Create a .env file at the root of your project:
PORT=4567
API_TOKEN=your_secret_token
Then initialize the manager with no arguments and it will pick everything up automatically:
require 'gr_api_manager'
api = GRApiManager::Server.new
# Reads PORT and API_TOKEN from .env
This is the recommended approach for production — keep secrets out of source code and out of version control. Add .env to your .gitignore.
# .gitignore
.env
Priority order
When a value is provided both in code and in .env, the explicit argument always wins:
new(bearer_token: "hardcoded") > ENV['API_TOKEN'] > nil (auth disabled)
new(port: 4567) > ENV['PORT'] > 4000
Defining Routes
The manager exposes get, post, put, and delete methods. Each route receives a merged params hash containing both URL/query parameters (type-cast) and the parsed JSON body.
api.get('/users', auth: true, requires: [:role, :age]) do |params|
# params is a merged, type-cast hash of all inputs
{
requested_role: params[:role],
is_adult: params[:age] >= 18 # age is safely cast to Integer
}
end
Options
| Option | Type | Default | Description |
|---|---|---|---|
auth |
Boolean | true |
Require Bearer Token for this route |
requires |
Array | [] |
List of required parameter keys (symbols/strings) |
Authentication
All routes require a valid Bearer Token by default. Pass the token in the Authorization header:
curl -H "Authorization: Bearer your_secret_token" http://localhost:4567/api/v1/users
To make a route public, set auth: false:
api.get('/health', auth: false) do
{ status: 'online' }
end
Automatic Error Responses:
// Missing header (401)
{ "error": "Token required. Format: 'Bearer <token>'" }
// Wrong token (403)
{ "error": "Invalid token" }
Parameter Validation
Declare required parameters at the route level. If any are missing or blank, the framework responds with 400 Bad Request automatically — no conditional logic needed in your handler.
api.post('/users', requires: [:name, :email]) do |params|
status 201
{ message: "User created", user: params }
end
curl -X POST http://localhost:4567/api/v1/users \
-H "Authorization: Bearer secret" \
-H "Content-Type: application/json" \
-d '{"name": "Gabo"}'
# => 400 { "error": "Missing required parameters", "required": ["email"] }
Smart Type Casting
Query string parameters are automatically cast to native Ruby types before reaching your handler.
| Input string | Ruby type | Value |
|---|---|---|
"42" |
Integer | 42 |
"3.14" |
Float | 3.14 |
"true" |
TrueClass | true |
"false" |
FalseClass | false |
"hello" |
String | "hello" |
curl "http://localhost:4567/api/v1/test/types?age=18&active=true&score=9.5" \
-H "Authorization: Bearer secret"
# => { "age": 18, "active": true, "score": 9.5 }
Error Handling
Global handlers return consistent JSON for unmatched routes and unhandled exceptions.
// 404
{ "error": "Endpoint not found", "path": "/api/v1/missing" }
// 500
{ "error": "Internal Server Error", "details": "..." }
You can also set status codes and short-circuit responses manually inside any handler:
api.get('/users/:id') do |params|
if params[:id] == 0
status 404
next { error: "Invalid user ID" }
end
{ id: params[:id], name: "User_#{params[:id]}" }
end
Request Logging
Every request is logged to stdout with a timestamp and color-coded status:
[14:32:01] GET /api/v1/health - 200
[14:32:05] POST /api/v1/users - 400
Green for 2xx, red for everything else.
Running the Server
api.run!
=============================================
GR API MANAGER STARTED
Port : 4567
Auth : Enabled
Prefix : /api/v1
=============================================
Full Usage Example
Below is a complete working API covering the most common patterns.
require 'gr_api_manager'
api = GRApiManager::Server.new(
port: 4567,
bearer_token: "secret123",
prefix: "/api/v1"
)
# 1. Public health check — no token required
api.get('/health', auth: false) do
{ status: 'online', time: Time.now.strftime("%Y-%m-%d %H:%M:%S") }
end
# 2. List users — token required (default)
api.get('/users') do |params|
users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "viewer" }
]
{ users: users, total: users.length }
end
# 3. Get single user by ID — :id is cast to Integer automatically
api.get('/users/:id') do |params|
if params[:id] == 0
status 404
next { error: "User not found" }
end
{ id: params[:id], name: "User_#{params[:id]}" }
end
# 4. Create user — validates required fields before reaching the block
api.post('/users', requires: [:name, :role]) do |params|
status 201
{ message: "User created", user: { name: params[:name], role: params[:role] } }
end
# 5. Update user
api.put('/users/:id', requires: [:name]) do |params|
{ message: "User #{params[:id]} updated", name: params[:name] }
end
# 6. Delete user
api.delete('/users/:id') do |params|
{ message: "User #{params[:id]} deleted" }
end
api.run!
Minimal setup using only .env
# .env
PORT=4567
API_TOKEN=secret123
# api_server.rb
require 'gr_api_manager'
api = GRApiManager::Server.new # reads everything from .env
api.get('/ping', auth: false) { { pong: true } }
api.run!
License
The gem is available as open source under the terms of the MIT License.