Rails API Quickstart Guide  with PostgresSQL and JWT Tokens

Tags: ruby, rails, jwt, postgres
Published: 2020-06-28 21:13:44

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] (https://edgeguides.rubyonrails.org/generators.html#creating-generators-with-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](https://hub.packtpub.com/best-practices-for-restful-web-services-naming-conventions-and-api-versioning-tutorial/) 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] (https://jwt.io/) 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] (https://medium.com/@deryatanriverdii/the-secret-to-rails-creating-a-secret-key-in-your-rails-api-fd7540318045). 

*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.