Quick Start
This guide builds a Posts API with validation, filtering, sorting, pagination, and exports.
1. The Model
The API starts with a simple Post model:
rails generate model Post title:string body:text published:boolean
rails db:migrate# app/models/post.rb
class Post < ApplicationRecord
validates :title, presence: true
end2. API Definition
The API definition exposes posts as a resource:
# config/apis/api_v1.rb
Apiwork::API.define '/api/v1' do
export :openapi
export :typescript
export :zod
resources :posts
endThe export declarations tell Apiwork to generate documentation at /.openapi, /.typescript, and /.zod.
3. Routes
Apiwork is mounted in the routes:
# config/routes.rb
Rails.application.routes.draw do
mount Apiwork => '/'
endINFO
Apiwork uses the Rails router under the hood. The API definition's path (/api/v1) combines with the mount point (/) to produce routes like /api/v1/posts.
4. Representation
The representation defines how posts are serialized and what can be queried:
# app/representations/api/v1/post_representation.rb
module Api
module V1
class PostRepresentation < ApplicationRepresentation
attribute :id
attribute :title, writable: true, filterable: true
attribute :body, writable: true
attribute :published, writable: true, filterable: true
attribute :created_at, sortable: true
attribute :updated_at, sortable: true
end
end
endEach option does one thing:
writable: true— the attribute can be set in create/update requestsfilterable: true— the attribute can be filtered via query parameterssortable: true— the attribute can be sorted via query parameters
Types, nullability, and defaults are auto-detected from the database columns.
5. Contract
The contract connects to the representation and defines what each action accepts:
# app/contracts/api/v1/post_contract.rb
module Api
module V1
class PostContract < ApplicationContract
representation PostRepresentation
end
end
endrepresentation connects to PostRepresentation. The contract now knows:
- What attributes can be written (for create/update)
- What attributes can be filtered/sorted (for index)
- The types of all attributes (for validation)
6. Controller
The controller includes Apiwork::Controller and has two differences from standard Rails:
exposereturns datacontract.bodyprovides validated params
# app/controllers/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ApplicationController
include Apiwork::Controller
before_action :set_post, only: %i[show update destroy]
def index
expose Post.all
end
def show
expose post
end
def create
post = Post.create(contract.body[:post])
expose post
end
def update
post.update(contract.body[:post])
expose post
end
def destroy
post.destroy
expose post
end
private
attr_reader :post
def set_post
@post = Post.find(params[:id])
end
end
end
endTIP
Requests with undefined params are rejected. contract.query and contract.body contain only validated params.
7. Try It
Start the server:
rails serverCreate a post
curl -X POST http://localhost:3000/api/v1/posts \
-H "Content-Type: application/json" \
-d '{"post": {"title": "Hello World", "body": "My first post", "published": true}}'List posts
curl http://localhost:3000/api/v1/postsFilter and sort
# Only published posts
curl "http://localhost:3000/api/v1/posts?filter[published][eq]=true"
# Sort by newest first
curl "http://localhost:3000/api/v1/posts?sort[created_at]=desc"
# Paginate
curl "http://localhost:3000/api/v1/posts?page[number]=1&page[size]=10"Get the exports
curl http://localhost:3000/api/v1/.openapi
curl http://localhost:3000/api/v1/.typescript
curl http://localhost:3000/api/v1/.zodWhat You Got
The application now has:
- Validation — Requests are validated against the representation before reaching the controller
- Serialization — Responses are automatically formatted using the representation
- Filtering —
filterable: trueattributes can be filtered via?filter[field][op]=value - Sorting —
sortable: trueattributes can be sorted via?sort[field]=asc|desc - Pagination — Built-in offset-based pagination via
?page[number]=1&page[size]=10 - Exports — OpenAPI, TypeScript, and Zod exports generated from the same source
Additional Features
The quick start covered the basics. Other features include:
- Associations — eager loading via
?include[comments]=true - Nested writes — create or update related records in a single request
- Includes — control which associations appear in responses
- Advanced filtering — operators like
contains,starts_with, andAND/ORlogic - Cursor pagination — for large datasets
- Custom types — enums, unions, and polymorphic associations
Next Steps
- Adapters — filtering, sorting, pagination, and eager loading in depth
- Contracts — custom validation and action-specific params
- Representations — associations, computed attributes, and more