Table of Contents
- Setting Up a New Rails API Project
- Understanding RESTful Resources
- Generating Scaffold for CRUD Operations
- Customizing Endpoints
- Handling Parameters and Validation
- Authentication and Authorization
- Testing the API
- Documentation
- Deployment
- Conclusion
- References
1. Setting Up a New Rails API Project
Rails provides a built-in option to generate an API-focused application, skipping unnecessary frontend components (like views and assets). Let’s start by setting up the project.
Prerequisites
- Ruby (3.0+ recommended)
- Rails (7.0+ recommended)
- PostgreSQL (or SQLite for development; PostgreSQL is preferred for production)
Step 1: Install Rails (if not already installed)
gem install rails
Step 2: Create a New API Project
Use the --api flag to generate an API-only Rails app:
rails new blog_api --api -d postgresql
cd blog_api
--api: Configures Rails for API development (disables Action View, sprockets, etc.).-d postgresql: Sets PostgreSQL as the database (replace with-d sqlite3for SQLite).
Step 3: Configure CORS
APIs are often accessed by frontend apps on different domains. Enable Cross-Origin Resource Sharing (CORS) to allow this:
- Open
config/initializers/cors.rband uncomment the default configuration:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*' # Restrict to specific domains in production (e.g., 'https://your-frontend.com')
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Step 4: Set Up the Database
Initialize the PostgreSQL database:
rails db:create
2. Understanding RESTful Resources
RESTful APIs organize data as “resources” (e.g., articles, users) and use HTTP methods to interact with them. Rails follows REST conventions by mapping CRUD operations to HTTP verbs and routes:
| Action | HTTP Method | Purpose | Example Route |
|---|---|---|---|
index | GET | List all resources | GET /api/articles |
show | GET | Show a single resource | GET /api/articles/1 |
create | POST | Create a new resource | POST /api/articles |
update | PUT/PATCH | Update a resource | PATCH /api/articles/1 |
destroy | DELETE | Delete a resource | DELETE /api/articles/1 |
3. Generating Scaffold for CRUD Operations
Rails scaffolding auto-generates models, controllers, and routes for a resource. Let’s build an API for managing Article resources (with title and body).
Step 1: Generate Scaffold
Run the scaffold generator:
rails generate scaffold Article title:string body:text
This creates:
- A model
Articlewithtitle(string) andbody(text). - A controller
ArticlesControllerwith RESTful actions (index,show,create, etc.). - Database migration for the
articlestable.
Step 2: Run the Migration
Execute the migration to create the articles table:
rails db:migrate
Step 3: Inspect Routes
Check the generated routes with:
rails routes
You’ll see RESTful routes for articles, prefixed with /articles by default. To namespace routes under /api (recommended for APIs), update config/routes.rb:
Rails.application.routes.draw do
namespace :api do
resources :articles # Maps to /api/articles, /api/articles/1, etc.
end
end
Now, routes like GET /api/articles will trigger Api::ArticlesController#index.
Step 4: Update the Controller Namespace
Move app/controllers/articles_controller.rb to app/controllers/api/articles_controller.rb and update the class name:
class Api::ArticlesController < ApplicationController
# Scaffold-generated actions (index, show, create, etc.)
end
4. Customizing Endpoints
Sometimes you need non-standard endpoints (e.g., publish an article). Let’s add a custom action to mark an article as “published.”
Step 1: Add a published Attribute to Article
Generate a migration to add a published boolean column:
rails generate migration AddPublishedToArticles published:boolean:index
rails db:migrate
Set a default value for published (e.g., false) by updating the migration before running rails db:migrate:
def change
add_column :articles, :published, :boolean, default: false, null: false
add_index :articles, :published
end
Step 2: Add a Custom Route
Update config/routes.rb to define a publish action for individual articles:
namespace :api do
resources :articles do
member do
put :publish # Maps to PUT /api/articles/:id/publish
end
end
end
Step 3: Implement the publish Action
Add the publish method to Api::ArticlesController:
def publish
@article = Article.find(params[:id])
@article.update(published: true)
render json: @article
end
5. Handling Parameters and Validation
Strong Parameters
Rails requires “strong parameters” to prevent mass assignment vulnerabilities. Permit only allowed attributes in the controller:
class Api::ArticlesController < ApplicationController
# ...
private
def article_params
params.require(:article).permit(:title, :body) # Allow only :title and :body
end
end
Model Validation
Ensure data integrity with model validations. Update app/models/article.rb:
class Article < ApplicationRecord
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
end
Handle Validation Errors in Responses
Update the create and update actions to return meaningful errors when validation fails:
def create
@article = Article.new(article_params)
if @article.save
render json: @article, status: :created # 201 Created
else
render json: { errors: @article.errors.full_messages }, status: :unprocessable_entity # 422 Unprocessable Entity
end
end
6. Authentication and Authorization
Secure your API with token-based authentication. We’ll use Rails’ has_secure_token to generate unique tokens for users.
Step 1: Generate a User Model
rails generate model User email:string:index password_digest:string token:string:index
rails db:migrate
password_digest: For secure password storage (viahas_secure_password).token: For API authentication (viahas_secure_token).
Step 2: Configure Authentication in User Model
Update app/models/user.rb:
class User < ApplicationRecord
has_secure_password # Enables password hashing (requires bcrypt gem)
has_secure_token # Generates a unique `token` attribute
validates :email, presence: true, uniqueness: true
end
Add bcrypt to your Gemfile (required for has_secure_password):
gem 'bcrypt'
Run bundle install.
Step 3: Authenticate Requests
Add an authentication check to Api::ApplicationController (create this file if it doesn’t exist):
class Api::ApplicationController < ActionController::API
before_action :authenticate_user
private
def authenticate_user
token = request.headers['Authorization']&.split(' ')&.last # Expecting "Bearer <token>"
@current_user = User.find_by(token: token)
return if @current_user
render json: { error: 'Unauthorized' }, status: :unauthorized # 401 Unauthorized
end
end
Update Api::ArticlesController to inherit from Api::ApplicationController:
class Api::ArticlesController < Api::ApplicationController
# Actions will now require authentication
end
Step 4: Authorize Actions (Optional)
Ensure users can only modify their own articles. Add a user:references to Article:
rails generate migration AddUserToArticles user:references
rails db:migrate
Update Article and User models for associations:
# app/models/article.rb
belongs_to :user
# app/models/user.rb
has_many :articles
Modify ArticlesController to scope articles to the current user:
def index
@articles = @current_user.articles # Only return the current user's articles
render json: @articles
end
def create
@article = @current_user.articles.new(article_params) # Associate article with current user
# ...
end
7. Testing the API
Test your API using Rails’ built-in Minitest or RSpec. Here’s an example Minitest for the create action:
Step 1: Generate a Controller Test
rails generate test_controller api/articles
Step 2: Write Tests
Update test/controllers/api/articles_controller_test.rb:
require 'test_helper'
class Api::ArticlesControllerTest < ActionDispatch::IntegrationTest
setup do
@user = User.create(email: '[email protected]', password: 'password')
@auth_headers = { 'Authorization': "Bearer #{@user.token}" }
end
test "should create article" do
assert_difference('Article.count') do
post api_articles_url,
params: { article: { title: 'Test Title', body: 'Test body' } },
headers: @auth_headers,
as: :json
end
assert_response :created
end
test "should not create article with invalid params" do
post api_articles_url,
params: { article: { title: '', body: '' } },
headers: @auth_headers,
as: :json
assert_response :unprocessable_entity
assert_not_empty JSON.parse(response.body)['errors']
end
end
Step 3: Run Tests
rails test
8. Documentation
Document your API so users know how to interact with it. Use RSwag to generate OpenAPI/Swagger docs.
Step 1: Add RSwag to Your Gemfile
group :development, :test do
gem 'rswag'
gem 'rswag-ui'
gem 'rswag-api'
end
Run bundle install and initialize RSwag:
rails generate rswag:install
Step 2: Write Swagger Docs
Create a spec file (e.g., spec/swagger/articles_spec.rb) to document endpoints:
require 'swagger_helper'
RSpec.describe 'API Articles', type: :request do
path '/api/articles' do
post 'Creates an article' do
tags 'Articles'
consumes 'application/json'
produces 'application/json'
parameter name: :article, in: :body, schema: {
type: :object,
properties: {
title: { type: :string },
body: { type: :string }
},
required: ['title', 'body']
}
response '201', 'article created' do
let(:article) { { title: 'Sample', body: 'Text' } }
run_test!
end
end
end
end
Step 3: Generate Docs
rails rswag
Access docs at http://localhost:3000/api-docs.
9. Deployment
Deploy your API to a platform like Heroku:
Step 1: Prepare for Heroku
- Add
Procfileto the root directory:
web: bundle exec puma -C config/puma.rb
- Ensure
Gemfileincludespg(PostgreSQL adapter) andrails_12factor(for Heroku):
gem 'pg', '~> 1.5'
gem 'rails_12factor', group: :production
Step 2: Deploy to Heroku
heroku create your-api-name
git push heroku main
heroku run rails db:migrate
Your API will be live at https://your-api-name.herokuapp.com/api/articles.
10. Conclusion
Ruby on Rails simplifies building RESTful APIs with its conventions, built-in tools (e.g., scaffold, strong parameters), and ecosystem (e.g., RSwag for docs, bcrypt for auth). By following this guide, you’ve created a secure, testable API with CRUD operations, custom endpoints, and documentation.