I recently sat down to create a Rails API and found myself googling like mad. At the end of the day, I had tabs galore and knew there had to be a better way.
This guide is intended for developers comfortable with Ruby on Rails. You will be able to set up a Rails API with CRUD functionality and (hopefully) not open a million tabs in the process.
Preview
- Create the API with a PostgresSQL Database
- Add the Project to Github
- Create the Schema, Models, and Routes
- Create Relationships Between Models
- Set Up Cors
- Create and Reorganize Controllers
- Create an AuthController to Handle JWT
- Change the Gem File
- Hide JWT Tokens
- Set Up Other Controllers
- Change Routes
- Get Everything Up and Running
Let's get started!
Creating the API and Hooking it up to PostgresSQL
You’ll want to start out cding into a folder to store your project.
In your terminal type:
rails new my-project --api --database=postgresql
This creates a new Rails project specifically for API use. This command populates specific gems in your gem folder and forgoes view files. It also hooks your database up to postgresSQL. You do not necessarily need to hook up your database to Postgres, but platforms such as Heroku require it.
Adding the New Project to Github
You can connect your project to your Github at any time. It's recommended to start early so you can save as you go.
In your Github account, create a new repository. Go back to your terminal, and while in your project directory, type the following:
git init
git add .
git commit -m “first commit”
git remote add origin <http url>
git remote -v
git push origin
git checkout -b new_branch
These commands link your project up to Github and ensure you have a new branch to work on.
Create Your Models, Routes, and Database
This section will be largely determined by the needs of your project. Rails documents a list of generators that you may find useful.
Since we are using bcrypt to encrypt passwords, write a password_digest column on the user table. The resource generator will create a user table, a model, and restful routes for a user.
rails generate resource User username:string password_digest:string
Create another resource generator for a post model. Notice the shorthand for generator and title--the word string can be omitted, but not any other data type.
rails g resource Post title content user_id:integer
Create Relations Between Models
In the users model, just below the class declaration, type the following:
has_many :posts
has_secure_password
Type the following in the post model:
belongs_to :user
A few things to take note of in these models is that has_many relationships take in a plural and the belongs_to does not. The has_secure_password validation gives us access to bycrypt methods in controller (more on that later) which relies on the password_digest from the previously created user table.
Setting up Cors
Cors is middleware that allows your app to make API calls from the front-end.
There’s three steps you need to do to hook this up.
Step 1.
In your project directory, navigate to your gem file and uncomment:
gem ‘rack-cors’
Step 2.
Navigate to config/application.rb and find this class declaration:
class Application < Rails::Application
Beneath the class declaration, paste this code. Do not modify anything else.
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post]
end
end
Step 3.
In config/initializers/cors.rb uncomment and change
origins ‘examples’
to
origins ‘*’
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
Cors errors can be incredibly frustrating to debug, so I recommend taking the time to carefully comb through the files to uncomment and add the code laid out in these steps.
Create and Reorganize Your Controllers
When we created our models using the generator command, we also created our controllers. However, these controllers are currently in the wrong directory. What’s up with API/V1? API convention states that you include the version in your url.
You will also need to add an AuthController which will manage user security.
From your terminal, navigate to the controllers directly and type the following:
mkdir api
cd api
mkdir v1
touch auth_controller.rb
Move all your controllers into the V1 directory except for ApplicationController.
Precede all your controllers with Api::V1:: like this example:
class Api::V1::UsersController < ApplicationController
This will ensure your routes are consistent.
Create an AuthController to Handle JWT Tokens and User Authentication
We are about half-way into creating our Rails API.
In the newly created AuthController, add the following code:
class Api::V1::AuthController < ApplicationController
skip_before_action :authorized, only: [:create]
def create
user = User.find_by(username: user_login_params[:username])
if user && user.authenticate(user_login_params[:password])
token = issue_token(user)
render json: {user: UserSerializer.new(user), jwt: token}
else
render json: {error: 'That user could not be found'}, status: 401
end
end
def show
user = User.find_by(id: user_id)
if logged_in?
render json: user
else
render json: { error: 'No user could be found'}, status: 401
end
end
private
def user_login_params
params.require(:user).permit(:username, :password)
end
end
A note on the methods in this class:
The AuthController’s job is to create a session for our user each time they log into the site.
User.authenticate comes from the bcrypt gem.
The skip_before_action validation ensures the user will be able to make an account via the create method. Issue token enables us to use JWT tokens, and is a method we’ll go over later in the application controller. We are using serializers here to better format our JSON.
The logged_in? action in the show method is also inherited from application controller.
Change the Gem File
To get access to some of these actions, go to your gem file and uncomment the following:
gem bcrypt
gem 'jbuilder', '~> 2.7'
In order to use JWT and serializers, add:
gem "jwt", "~> 2.2"
gem "active_model_serializers", "~> 0.10.10"
Please note that your gem versions may vary. Refer to the documentation for the most up-to-date versions.
Set Up and Hide Your JWT Token
Refrain from committing to github until after your secret key and the corresponding file is created.
JWT tokens are industry standard security credentials. These protocols are most commonly used for logging in users by storing a token in their localstorage. You can create your own jwt token by going to the website and changing your-256-bit-secret.
To create your JWT token (and hide it, too) go to your Application controller, add the following:
class ApplicationController < ActionController::API
before_action :authorized
def jwt_key
Rails.application.credentials.jwt_key
end
def issue_token(user)
JWT.encode({user_id: user.id}, jwt_key, 'HS256')
end
def decoded_token
begin
JWT.decode(token, jwt_key, true, { :algorithm => 'HS256' })
rescue JWT::DecodeError
[{error: "Invalid Token"}]
end
end
def authorized
render json: { message: 'Please log in' }, status: :unauthorized unless logged_in?
end
def token
request.headers['Authorization']
end
def user_id
decoded_token.first['user_id']
end
def current_user
@user ||= User.find_by(id: user_id)
end
def logged_in?
!!current_user
end
end
Notice the jwt_key variable? We will be hiding this code with the following command.
Hide the secret key by typing in your terminal:
EDITOR="code --wait" rails credentials:edit
Add the hash:
jwt_key: your-256-bit-secret
You can also hide other information in the same way. Once you are done adding your secret keys, save and close the file. For a more in-depth look at how Rails credentials work, check out Derya Tanriverdi ‘s tutorial The Secret to Rails.
You are now free to resume committing to Github.
Having trouble accessing this secret key when migrating to Heroku? Try this command out in your heroku terminal:
heroku config:set RAILS_MASTER_KEY=<your master key>
Your master key is NOT the JWT token key we created, but the Rails master key found in the masterkey file.
Set Up Your Other Controllers
Code the RESTful actions that seem appropriate to your controllers: index, create, show, update, destroy. Your UsersController should also use the jwt_token and current_user methods we created in the application controller.
class Api::V1::UsersController < ApplicationController
skip_before_action :authorized, only: [:create]
def profile
@user = User.find(params[:id])
render json: {user: current_user}
end
def create
@user = User.new(user_params)
if @user.valid?
@user.save
@token = encode_token(@user)
render json: { user: @user, jwt: @token }
else
render json: { error: 'failed to create user' }, status: :not_acceptable
end
end
Add Serializers (optional)
Serializers are a good idea for projects with a number of associations. You can generate a serializer from the terminal using the rails g command.
rails g serializer user
This will create a serializer folder and file.
In your newly created serializer, write the following:
class UserSerializer < ActiveModel::Serializer
attributes :id, :username
end
Depending on your project's needs, you may need to add additional methods and attributes.
Change Routes
Rails created routes for you through the resources generator. However, these routes are in the wrong places. Reorganize the file and create custom routes for the auth and user controllers.
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
namespace :api do
namespace :v1 do
resources :posts
resources :users
post '/login', to: 'auth#create'
get '/current_user', to: 'auth#show'
post '/sign_up', to: 'users#create'
end
end
end
Getting Rails Up and Running
Finally your Rails app is ready to function as an API. Run these last commands from the terminal.
Bundle install
Rails db:create
Rails db:migrate
Rails s
Summary
And there you have it! We set up a Rails API by connecting it to a postgresSQL database, added JWT tokens for user security, and added serializers. In addition, we connected our project to github, hid our secret key, changed routes, and enabled cors and bcrypt. As a final exercise, use Postman to test out your routes, or use the Rails console to create a few users.
Top comments (1)
Hi Kailana
Thanks for this nice posting
I am looking forward to your next one