TL;DR: RapiTapir brings Tapir-style, declarative, type-safe APIs to Ruby with a clean DSL, automatic OpenAPI docs, a TypeScript client, and a new one-command scaffold from an OpenAPI spec.
Ruby developers love the language’s expressiveness, but API development often involves:
What if we could have the type safety of languages like Scala while keeping Ruby’s elegance? Enter RapiTapir 🦙
RapiTapir is inspired by Scala’s Tapir, bringing declarative, type-safe API development to Ruby. Define your endpoints once with strong typing, and get automatic validation, documentation, and client generation.
require 'rapitapir'
class BookAPI < RapiTapir::SinatraRapiTapir
rapitapir do
info(title: 'Book API', version: '1.0.0')
development_defaults! # health checks, CORS, docs
end
# T shortcut available globally
BOOK_SCHEMA = T.hash({
"id" => T.integer,
"title" => T.string(min_length: 1, max_length: 255),
"author" => T.string(min_length: 1),
"published" => T.boolean,
"isbn" => T.optional(T.string),
"pages" => T.optional(T.integer(minimum: 1))
})
endpoint(
GET('/books')
.query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)))
.query(:genre, T.optional(T.string))
.summary('List books with filtering')
.ok(T.array(BOOK_SCHEMA))
.build
) do |inputs|
Book.where(genre: inputs[:genre])
.limit(inputs[:limit] || 20)
.map(&:to_h)
end
end
Start the server and visit http://localhost:4567/docs for interactive Swagger documentation that’s always in sync with your code.
# One line to get a complete API server
class MyAPI < SinatraRapiTapir
# Enhanced HTTP verbs automatically available
# T shortcut for types works everywhere
# Health checks, CORS, docs - all included
end
# Define once, use everywhere
USER_SCHEMA = T.hash({
"email" => T.email, # built-in email validation
"age" => T.optional(T.integer(minimum: 0, maximum: 150))
})
# Automatic validation + coercion
endpoint(POST('/users').json_body(USER_SCHEMA).build) do |inputs|
User.create(inputs[:body])
end
# Complete CRUD API in ~10 lines
api_resource '/books', schema: BOOK_SCHEMA do
crud do
index { Book.all }
show { |inputs| Book.find(inputs[:id]) }
create { |inputs| Book.create(inputs[:body]) }
update { |inputs| Book.update(inputs[:id], inputs[:body]) }
destroy { |inputs| Book.destroy(inputs[:id]); status 204 }
end
# Add custom endpoints with full type safety
custom :get, 'featured' do
Book.where(featured: true)
end
end
Input: an OpenAPI/Swagger JSON. Output: a runnable SinatraRapiTapir + ActiveRecord + SQLite app with:
Try it:
rapitapir generate scaffold --from openapi.json --out ./my_api
cd my_api
bundle install
bundle exec rake db:create db:migrate
bundle exec rackup
Works with your existing Ruby stack:
# Sinatra (recommended) - clean inheritance
class API < RapiTapir::SinatraRapiTapir; end
# Sinatra extension
register RapiTapir::Sinatra::Extension
RapiTapir builds on Ruby’s strengths:
It’s not about replacing your stack - it’s about making it better.
gem install rapitapir
Or add to your Gemfile:
gem 'rapitapir'
Check out the examples and docs:
RapiTapir is production-ready with comprehensive tests, clear documentation, and real-world examples. Whether you’re building a new API or enhancing an existing one, RapiTapir helps you write better Ruby code.
Links:
Built with ❤️ for the Ruby community. Questions? Feedback? Open an issue or discussion on GitHub!
RapiTapir - APIs so clean and fast, they practically run wild! 🦙⚡
Subscribe to get future posts via email (or grab the RSS feed)