RapiTapir 🦙 combines the expressiveness of Ruby with the safety of strong typing to create APIs that are both powerful and reliable. Define your endpoints once with our fluent DSL, and get automatic validation, documentation, and client generation.
✨ Clean Base Class: class MyAPI < SinatraRapiTapir - the simplest way to create APIs
🎯 Enhanced HTTP DSL: Built-in GET, POST, PUT, DELETE methods with fluent chaining
🔧 Zero Boilerplate: Automatic extension registration and feature setup
� Type Shortcuts: Use T.string instead of RapiTapir::Types.string for cleaner code
�📚 GitHub Pages Ready: Modern documentation deployment with GitHub Actions
🧪 Comprehensive Tests: 470 tests passing with 70% coverage
🔒 Type Safety: Strong typing for inputs and outputs with runtime validation
📖 Auto Documentation: OpenAPI 3.0 specs generated automatically from your code
🚀 Framework Agnostic: Works with Sinatra, Rails, and any Rack-based framework
🛡️ Production Ready: Built-in security, observability, and authentication features
💎 Ruby Native: Designed specifically for Ruby developers who love clean, readable code
🔧 Zero Config: Get started in minutes with sensible defaults
✨ Clean Syntax: Elegant base class: class MyAPI < SinatraRapiTapir
� Type Shortcuts: Clean type syntax with T.string, T.integer, etc.
�🔄 GitHub Pages: Modern documentation deployment with GitHub Actions
Add to your Gemfile:
require'rapitapir'# Only one require needed!classBookAPI < SinatraRapiTapir# Configure API informationrapitapirdoinfo(title: 'Book API',description: 'A simple book management API',version: '1.0.0')development_defaults!# Auto CORS, docs, health checksend# Define your data schema with T shortcut (globally available!)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))})# Define endpoints with the elegant resource DSL and enhanced HTTP verbsapi_resource'/books',schema: BOOK_SCHEMAdocruddoindex{Book.all}showdo |inputs|
Book.find(inputs[:id]) || halt(404,{error: 'Book not found'}.to_json)endcreatedo |inputs|
Book.create(inputs[:body])endend# Custom endpoint using enhanced DSLcustom:get,'featured'doBook.where(featured: true)endend# Alternative endpoint definition using enhanced HTTP verb DSLendpoint(GET('/books/search').query(:q,T.string(min_length: 1),description: 'Search query').query(:limit,T.optional(T.integer(minimum: 1,maximum: 100)),description: 'Results limit').summary('Search books').description('Search books by title or author').tags('Search').ok(T.array(BOOK_SCHEMA)).bad_request(T.hash({"error"=>T.string}),description: 'Invalid search parameters').build)do |inputs|
query=inputs[:q]limit=inputs[:limit] || 20books=Book.search(query).limit(limit)books.map(&:to_h)endrun!if__FILE__ == $0
end
That's it! You now have a fully documented, type-safe API with interactive documentation.
Create APIs with the cleanest possible syntax:
require'rapitapir'classMyAPI < SinatraRapiTapirrapitapirdoinfo(title: 'My API',version: '1.0.0')development_defaults!# Auto CORS, docs, health checksend# Enhanced HTTP verb DSL automatically available + T shortcut for typesendpoint(GET('/books').summary('List all books').ok(T.array(BOOK_SCHEMA)).error_response(500,T.hash({"error"=>T.string})).build){Book.all}end
Define your data schemas once and use them everywhere:
# T shortcut is automatically available - no setup needed!USER_SCHEMA=T.hash({"id"=>T.integer,"name"=>T.string(min_length: 1,max_length: 100),"email"=>T.email,"age"=>T.optional(T.integer(min: 0,max: 150)),"profile"=>T.optional(T.hash({"bio"=>T.string(max_length: 500),"avatar_url"=>T.string(format: :url)}))})
Fluent Endpoint Definition
Create endpoints with a clean, readable DSL:
# Using the enhanced HTTP verb DSL with T shortcutendpoint(GET('/users/:id').summary('Get user by ID').path_param(:id,T.integer(minimum: 1)).query(:include,T.optional(T.array(T.string)),description: 'Related data to include').ok(USER_SCHEMA).error_response(404,T.hash({"error"=>T.string}),description: 'User not found').error_response(422,T.hash({"error"=>T.string,"details"=>T.array(T.hash({"field"=>T.string,"message"=>T.string}))})).build)do |inputs|
user=User.find(inputs[:id])halt404,{error: 'User not found'}.to_jsonunlessuser# Handle optional includesifinputs[:include]&.include?('profile')user=user.with_profileenduser.to_hend
Build complete CRUD APIs with minimal code:
# Enhanced resource builder with custom validations and relationshipsapi_resource'/users',schema: USER_SCHEMAdocruddoindexdo# Automatic pagination and filteringusers=User.allusers=users.where(active: true)ifparams[:active] == 'true'users.limit(params[:limit] || 50)endshow{ |inputs| User.find(inputs[:id])}createdo |inputs|
user=User.create(inputs[:body])status201user.to_hendupdate{ |inputs| User.update(inputs[:id],inputs[:body])}destroy{ |inputs| User.delete(inputs[:id]);status204}end# Add custom endpoints with full type safetycustom:get,'active'doUser.where(active: true).map(&:to_h)endcustom:post,':id/avatar'do |inputs|
user=User.find(inputs[:id])user.update_avatar(inputs[:body][:avatar_data]){success: true}endend
Automatic OpenAPI Documentation
Your API documentation is always up-to-date because it's generated from your actual code:
Interactive Swagger UI with try-it-out functionality
Complete OpenAPI 3.0 specification with schemas, examples, and security
RapiTapir includes comprehensive testing utilities:
# Validate your endpoint definitionsRapiTapir::CLI::Validator.new(endpoints).validate# Generate test fixturesRapiTapir::Testing.generate_fixtures(USER_SCHEMA)