Riccardo Merolla

August 11, 2025

πŸ¦™ Introducing RapiTapir: Type-Safe HTTP APIs for Ruby

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.


The Problem: Ruby API Development Pain Points

Ruby developers love the language's expressiveness, but API development often involves:

  • Manual documentation that gets out of sync with code
  • Runtime errors from type mismatches that could be caught earlier
  • Boilerplate code for validation, serialization, and error handling
  • Inconsistent patterns across different teams and projects
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.


The Magic: Clean Base Class Syntax

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.


Why RapiTapir Feels Like Ruby Magic ✨


1. Zero Boilerplate Setup

# 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


2. Type Safety Without Ceremony

# 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


3. RESTful Resources Made Simple

# 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


From OpenAPI to Running App (Scaffold)

Input: an OpenAPI/Swagger JSON.

Output: a runnable SinatraRapiTapir + ActiveRecord + SQLite app with:

  • config.ru, Rakefile, Gemfile
  • app/api/app.rb (endpoints mapped; docs, tags, descriptions, and security reflected)
  • app/models, db/migrate, config/database.yml
  • health check and docs enabled by default
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


Production-Ready Features πŸ›‘οΈ

  • Development defaults: health checks, CORS, and docs out of the box.
  • Security headers: header-based API keys in your OpenAPI are scaffolded and reflected in docs.
  • Observability: health endpoint enabled; metrics/tracing integration planned.

Real-World Benefits

πŸš€ Development Speed: Define endpoints declaratively, get validation + docs for free

πŸ› Fewer Bugs: Type checking catches issues at definition time, not runtime

πŸ“– Always-Current Docs: Swagger UI generated from your actual code

πŸ”§ Better DX: Enhanced error messages, auto-completion, consistent patterns

⚑ Easy Testing: Validate schemas independently, generate test fixtures


Framework Integration

Works with your existing Ruby stack:

# Sinatra (recommended) - clean inheritance
class API < RapiTapir::SinatraRapiTapir; end

# Sinatra extension
register RapiTapir::Sinatra::Extension


The Ruby Community Connection

RapiTapir builds on Ruby's strengths:

  • Sinatra's simplicity with enhanced capabilities
  • Rack's composability for middleware and deployment
  • Ruby's expressiveness with added type safety
  • Community gems for auth, testing, deployment
It's not about replacing your stack - it's about making it better.


Getting Started

gem install rapitapir

Or add to your Gemfile:

gem 'rapitapir'

Check out the examples and docs:


What’s Next?

  • Rails integration for seamless adoption in existing apps
  • More client generators (Python, Go)
  • Enhanced observability and tracing
  • Plugin ecosystem for community extensions

Try It Today

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! πŸ¦™βš‘