How to use this Tutorial:
Advanced Tutorial that requires some basic knowledge in:
Technical and installation requirements:
We will use have this folder structure:
grad_project
├── api # <-- Rails API app
│ ├── app
│ │ ├── controllers
│ │ │ └── api
│ │ │ └── v1
│ │ │ └── posts_controller.rb
│ │ └── models
│ └── post.rb
├── client # <-- ReactTS app
│ └── src
...
With Ruby on Rails (RoR), the standard rails new
generator usually creates a full MVC RoR app, with RoR Views.
In order to use a different Frontend framework, it is best practice to make the RoR app API-only - that is, only Model and Controllers (and other middleware) but no Views.
You can generate this with:
rails new [API_APP_NAME] --api -T
Note: I usually specify the -T
flag to prevent autogeneration of Minitest
unit test, as I prefer to use RSpec
for tests (more about RSpec)
The --api
flag specifies this is an API-only app, preventing autogeneration of Views
bundle install
and other configurations you may do nowCross Origin Resource Sharing (CORS) is a form of security that checks the origin (URL) of HTTP requests.
The app that we will build will have two origin URLs - in our local development environment, this will most likely be localhost:3000
for the RoR app and localhost:3001
for the ReactTS app.
As such, you need to update the CORS policy in the RoR app to allow requests from the ReactTS app we will build
gem 'rack-cors'
in the gemfile
bundle install
config/initializers/cors.rb
and update the origins
line to origins '*'
- this will accept requests from any origin. The file should look like this:Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
localhost:3001
, as well as for a future deployed app URL (as a comma-delimited list of string URLS)For automated testing, we will use RSpec (official docs). It is important to set this up now, as RSpec will autogenerate test files when you generate Models and Controllers later on.
gem 'rspec-rails'
to the gemfile
group :development, :test do ... end
block, so the test packages aren't used in the deployed appgem 'factory_bot_rails'
gem 'simplecov'
bundle install
rails generate rspec:install
to generate boilerplate configurationsThat's all for now.
For the purpose of this tutorial, we will create one Post model and one Post controller.
Note: from here, I will be using some shortened commands in RoR, like rails g
instead of rails generate
rails g model post title:string content:text
rails g controller posts index show create update delete
posts_controller
file with empty methods called index, show, create, update and delete.rails db:create db:migrate
config/database.yml
file.We will use RESTful API naming convention best practice for versioning and move the posts_controller.rb
file to a new folder. You can read more on this stack overflow post.
controllers
folder, create a new folder api
api
folder, create a new folder called v1
posts_controller.rb
to the v1
folderposts_controller.rb
file, change class PostsController < ApplicationController
to class Api::V1::PostsController < ApplicationController
To help with learning, we will add some comments to the posts_controller.rb
file to show where these methods should route to:
class Api::v1::PostsController < ApplicationController
# GET /posts
def index
end
# GET /posts/:id
def show
end
# POST /posts
def create
end
# PATCH/PUT /posts/:id
def update
end
# DELETE /posts/:id
def destroy
end
end
Now it's time to update the routes.rb
file:
routes.rb
file, add:namespace :api do
namespace :v1 do
end
end
resources :posts, only: [:index, :show, :create, :update, :delete]
resources
keyword handles the route mapping, so you don't need to specify each HTTP request methodAt the moment, we don't have any data in the db to test our post_controller.rb
endpoints.
We will create/update the db/seeds.rb
file to create a few posts for us to test
db/seeds.rb
file add require 'faker
times
iterator, add this block:10.times do
end
post
variable that we will create a Post instance in:post = Post.create(
title: Faker::Lorem.sentence(word_count: 3),
content: Faker::Lorem.paragraph(sentence_count: 2)
)
print
statements to the file to get feedback when instances are created e.g. add print 'Created #{post}'
in the Post.create blockThe seed file should look something like this:
require 'faker'
10.times do
post = Post.create(
title: Faker::Lorem.sentence(word_count: 3),
content: Faker::Lorem.paragraph(sentence_count: 2)
)
print 'Created #{post}'
end
rails db:seed
to seed the databaseThere's one more thing that can improve the posts_controller.rb
file - callbacks and private methods.
before_action
callback to the posts_controller.rb
file:
set_post
method before the show
, update
and destroy
methodsbefore_action :set_post, only: [:show, :update, :destroy]
set_post
to the posts_controller.rb
file:
:id
param in the URLprivate
def set_post
@post = Post.find(params[:id])
end
NOTE: place all your private methods at the bottom of the controller file, below the private
keyword
We also need to add a post_params
method to the posts_controller.rb
file.
Why? This is a security measure to prevent malicious users from sending requests with extra parameters that we don't want to be saved in the database.
post_params
to the posts_controller.rb
file:
title
and content
parameters to be saved in the databasedef post_params
params.require(:post).permit(:title, :content)
end
Whenever you add new fields to the database, you will need to update this method to allow those fields to be saved.
Now we can write the CRUD methods in the posts_controller.rb
file.
index
method, add:@posts = Post.all
render json: @posts
show
method, add:render json: @post
create
method, add:@post = Post.new(post_params)
if @post.save
render json: @post, status: :created
else
render json: @post.errors, status: :unprocessable_entity
end
update
method, add:if @post.update(post_params)
render json: @post
else
render json: @post.errors, status: :unprocessable_entity
end
destroy
method, add:@post.destroy
NOTE: notice that we don't need to render anything in the destroy
method, as the Post instance is already deleted
posts_controller_spec.rb
fileUsing a third party app called Postman, we can test the API endpoints we just created.
GET
and the URL to localhost:3000/api/v1/posts
Send
and you should see a list of all the posts in the databasePOST
, PATCH/PUT
and DELETE
with the appropriate URLs and HTTP request methodsThis is an important step to test the API endpoints before we move on to the ReactTS app.
We can also add custom endpoints to the posts_controller.rb
file.
For example, we can add a search
endpoint that will search for posts with a specific title.
posts_controller.rb
file, add:def search
@posts = Post.where('title LIKE ?', '%#{params[:title]}%')
render json: @posts
end
routes.rb
file, add to the :v1
block:get '/search', to: 'posts#search'
search
method in the posts_controller_spec.rb
fileNOTE: in the case of a Search endpoint, you may want to use a gem called pg_search to improve the search functionality
At this point, you should have:
Additionally, after manually testing the endpoints, you should be able to see the JSON response in Postman.
Ruby on rails
Backend