---
# System prepended metadata

title: API Development
tags: [API, ' Ruby', ' Ruby on Rails']

---

---
title: API Development
tags: API, Ruby, Ruby on Rails
description: Step by step API development with Ruby on Rails.
---

[![hackmd-github-sync-badge](https://hackmd.io/MLgY349iTpWj8x3IUvytfQ/badge)](https://hackmd.io/MLgY349iTpWj8x3IUvytfQ)


<style>
code.blue {
  color: #337AB7 !important;
}
code.orange {
  color: #F7A004 !important;
}
code.green {
  color: #008000 !important;
}
code.red {
  color: #FF0000 !important;
}
</style>

# To the :moon: !

<!-- Put the link to this slide here so people can follow -->
slide: https://hackmd.io/@hanmaslah/H1VvcpP7O

---

We have a collaborative session

Please prepare laptop to join!

---

## Who am I?

- Senior Ruby on Rails developer :female-astronaut: 
- VSCode :heart: 
- I use tabs. :cat:
- Ruby is my love :sunflower: 

---

## Developers :heart: GitHub.

If you haven't done so, follow the tutorial about WLS and GIT and Rails Installation

---

Remind me to put a video here
<!-- {%youtube E8Nj7RwXf0s %} -->

---

# Table of Contents

- Create a rails app, db=pg, api-only
- Create User model
    - username and password
- Create conversation model
    - owner
- Create messages model
    - conversation, author, body


---

Create the API
```shell=
rails new forum --api --skip-test-unit --database=postgresql
```

---

CD into the Rails folder

```shell=
cd forum
```

---

Create the database

```shell=
rails db:create
```

---

Always GIT everything. If GIT is not initiated by default, git init and link with remote repository

```shell=
git init
git remote add origin git@github.com:hmasila/forum.git
```

---

Commit the generated files

```shell=
git add .
git commit -m 'initial commit'
git push origin master
```

---

<code class="blue">Do a happy dance and get back to work</code>

![Ruby dance](https://media1.tenor.com/images/6445ee2274a782a7c528303e9bd823d7/tenor.gif?itemid=9878005)

---

# Models

---

Rails uses [Active Record](https://guides.rubyonrails.org/active_record_basics.html) as the ORM Framework

Model is the layer of the system responsible for representing business data and logic.

---

We have 3 models. The user, conversation and messages

<code class="orange">

- A conversation belongs to the owner
- A conversation has many users through messages
- A conversation has many messages
- A user has many authored_conversations as author
- A user has many messages as author
- A user has many conversations through messages
- A message belongs to a conversation
- A message belongs to a user as contributor

</code>

---

## User

---

Generate the user model

```cmd=
rails generate scaffold User username:string password_digest:string avatar_url:string
```

---

This will generate a bunch of files. In a rails app, scaffold follows the MVC architecture but since we are creating an API, we only have M and C

So the files created will be the model, controller, database migration and the routes file is updated to include the user's routes.

---

The migration file looks like this

```ruby
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :username, null: false, index: { unique: true }
      t.string :password_digest, null: false
      t.string :avatar_url

      t.timestamps
    end
  end
end

```

---

Run the migration. This will update the schema with the new data.

```shell=
rails db:migrate
```

---

The password_digest attribute depends on bcrypt
Add the following line to your Gemfile

```ruby=
gem 'bcrypt', '~> 3.1.7'
```

---

Run bundler

```shell=
bundle install
```

---

### Validations

---

Add these lines to your User model

```ruby
has_secure_password
validates :username, presence: true, uniqueness: { case_sensitive: false }
```

---

[Secure Password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) creates the following validations:

- Password must be present on creation
- Password length should be less than or equal to 72 bytes
- Confirmation of password (using a password_confirmation attribute)


The username validation makes sure we only allow unique usernames to be created. It also makes sure the uniqueness is case insensitive. If we already have <code class="orange">JohnDoe</code> then <code class="orange">johndoe</code> is not acceptable

---

<code class="green">app/models/user.rb</code>

```ruby
class User < ApplicationRecord
  has_many :messages, inverse_of: :author, foreign_key: :author_id
  has_many :authored_conversations, inverse_of: :author, class_name: 'Conversation'
  has_many :conversations, through: :messages

  has_secure_password

  validates :username, presence: true, uniqueness: { case_sensitive: false }
end

```

---


At this point you can commit the new changes.

---

## Conversation

---

Generate the conversation model

```shell=
rails g scaffold Conversation user:references
```

---

The migration file looks like this

```ruby
class CreateConversations < ActiveRecord::Migration[6.0]
  def change
    create_table :conversations do |t|
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end

```

---

<code class="green">app/models/conversation.rb</code>

```ruby
class Conversation < ApplicationRecord
  has_many :messages
  has_many :users, through: :messages, source: :author
  belongs_to :author, class_name: 'User', inverse_of: :authored_conversations, foreign_key: :user_id
end

```

---

Run the migrations and commit the changes

---

## Message

---

Generate the message model

```shell=
rails g scaffold Message conversation:references user_id:integer body:text
```

---

The migration file looks like this

```ruby
class CreateMessages < ActiveRecord::Migration[6.0]
  def change
    create_table :messages do |t|
      t.references :conversation, null: false, foreign_key: true
      t.integer :contributor_id
      t.text :body

      t.timestamps
    end
  end
end

```

---

<code class="green">app/models/message.rb</code>

```ruby
class Message < ApplicationRecord
  belongs_to :conversation
  belongs_to :author, class_name: 'User', inverse_of: :messages
end

```

---

Run the migrations and commit the changes

---

# Console

---

To test out the relationships, use rails console

```shell=
rails console
```

---

## Create Users

---

```ruby=
User.create!(username: 'skywalker', password: 'passworD314', password_confirmation: 'passworD314')

User.create!(username: 'hansolo', password: 'passworD314', password_confirmation: 'passworD314')

User.create!(username: 'rookie', password: 'passworD314', password_confirmation: 'passworD314')
```

---

Try creating a user that doesn't meet the validations. Examples

```ruby=
# username already taken
User.create!(username: 'rookie', password: 'passworD314', password_confirmation: 'passworD314')

# no username
User.create!(password: 'passworD314', password_confirmation: 'passworD314')

# no password
User.create!(username: 'noPassword')

# wrong confirmation
User.create!(username: 'skywalker', password: 'passworD314', password_confirmation: 'wrongPassword')
```

---

## Create Conversation

---

```ruby=
# using user_id
Conversation.create(user_id: 1)

# using user
user = User.find(1)
user.authored_conversations.create!
```

---

## Create Messages

---

```ruby=
Message.create!(conversation_id: 1, author_id: 1, body: "I'm Luke Skywalker, I'm Here To Rescue You.")

# using conversation
conversation = Conversation.find(1)
conversation.messages.create(author_id: 2, body: "Hokey religions and ancient weapons are no match for a good blaster at your side, kid.")

# using user
user = User.find_by(username: 'skywalker')
user.messages.create(conversation_id: 1, body: "Strike Me Down In Anger And I'll Always Be With You.")
```

---

## Reading from Database

---

Here is some of the data we can retrieve

```ruby=
user = User.find_by(username: 'skywalker')
conversation = Conversation.find(1)
message = conversation.messages.last

user.authored_conversations
user.conversations
user.messages

conversation.messages
conversation.users
conversation.author

message.author
message.conversation
```

---

# Server

---

To start the server, run

```shell=
rails server
```

By now, you should be able to test out a few endpoints. 

---

We will be using [Postman](https://www.postman.com/) for API testing, so make sure you have the app or the agent installed.

Open postman and let us try to get users.

Enter [http://localhost:3000/users](http://localhost:3000/users) in the url field

Pick `GET` from the verb drop down.

Click `Send` and you should get a list of all the users.

---

![](https://i.imgur.com/EJwqzfI.png)


---

<code class="orange">You have successfully made an API!! 🥳 </code>

<code class="blue">Take a break. Stretch. Drink some water.</code>


![Ruby water break](https://media0.giphy.com/media/Bqn8Z7xdPCFy0/giphy.gif?cid=ecf05e472ewz8giaemn6n6rwtajunolak01pziyvqewdnso9&rid=giphy.gif&ct=g)

---

# Authentication

---

Token-based authentication is stateless authentication commonly implemented using JSON Web Authentication(JWT). It does not store anything on the server but creates a unique encoded token that gets checked every time a request is made.

Unlike session-based authentication, a token approach would not associate a user with login information but with a unique token that is used to carry client-host transactions.

---

Add this to the Gemfile

```ruby=
gem 'jwt', '~> 2.2.3'
```

---

Bundle it

```shell=
bundle install
```

---

Create a singleton class for the jwt_token_authentication

<code class="green">lib/json_web_token.rb</code>

```ruby
#lib/json_web_token.rb

class JsonWebToken
 class << self
   def encode(payload, exp = 24.hours.from_now)
     payload[:exp] = exp.to_i
     JWT.encode(payload, Rails.application.secrets.secret_key_base)
   end

   def decode(token)
     body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
     HashWithIndifferentAccess.new body
   rescue
     nil
   end
 end
end
```

---

We will be using the user ID, the expiration time (1 day), and the unique base key of your Rails application to create a unique token.

We will use this method for authenticating the user and generating a token for him/her using encode.

---

To decode the token, we will use the application's secret key.


We will use this method to check if the user's token appended in each request is correct.

---

Add the jwt singleton class to the application to be loaded

<code class="green">config/application.rb</code>

```ruby
config.autoload_paths <<  Rails.root.join("lib")
```

---

We will be using `simple_command` gem which facilitates the connection between the controller and the model.

Add this to the Gemfile and bundle install

```ruby=
gem 'simple_command', '~> 0.1.0'
```

Create a folder inside `app` and name it <code class="orange">commands</code>

---

## Authenticate user

This class will be called when the user is loging in. It's aim is to make sure the correct credentials are provided and return a token that will be used for other requests.

<code class="green">app/commands/authenticate_user.rb</code>

```ruby
# app/commands/authenticate_user.rb

class AuthenticateUser
  prepend SimpleCommand

  def initialize(username, password)
    @username = username
    @password = password
  end

  def call
    JsonWebToken.encode(user_id: user.id) if user
  end

  private

  attr_accessor :username, :password

  def user
    user = User.find_by(username: username)
    return user if user && user.authenticate(password)

    errors.add :user_authentication, 'invalid credentials'
    nil
  end
end
```

Prepend SimpleCommand in the simple_command class so that you can be able to use them in the controller classes.

---

## Authorization

This class will be called when a request is made. It checks if a token has been provided in the header and decodes it to make sure it is valid.

```ruby
# app/commands/authorize_request.rb

class AuthorizeRequest
  prepend SimpleCommand

  def initialize(headers = {})
    @headers = headers
  end

  def call
    user
  end

  private

  attr_reader :headers

  def user
    @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
    @user || errors.add(:token, 'Invalid token') && nil
  end

  def decoded_auth_token
    @decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
  end

  def http_auth_header
    if headers['Authorization'].present?
      return headers['Authorization'].split(' ').last
    else
      errors.add(:token, 'Missing token')
    end
    nil
  end
end
```

> [https://www.pluralsight.com/guides/token-based-authentication-with-ruby-on-rails-5-api]

---

Http requests have fields known as headers. Headers can contain a wide variety of information about the request that can be helpful for the server interpreting the request. Tokens are usually attached to the 'Authorization' header.

The command for authorization has to take the headers of the request and decode the token using the decode method in the JsonWebToken singleton.

---

## Login

This will be our first API endpoint. Users will use it to get a token that they can pass in the headers.

```ruby
# app/controllers/authentication_controller.rb

class AuthenticationController < ApplicationController
 skip_before_action :authenticate_request

 def authenticate
   command = AuthenticateUser.call(params[:username], params[:password])

   if command.success?
     render json: { auth_token: command.result }
   else
     render json: { error: command.errors }, status: :unauthorized
   end
 end
end
```

---

## Authorize Requests

To put the token to use, we will use a `current_user` method that will be the logged in user. In order to have current_user available to all controllers, it has to be declared in the ***ApplicationController***

---

```ruby
#app/controllers/application_controller.rb

class ApplicationController < ActionController::API
 before_action :authenticate_request
  attr_reader :current_user

  private

  def authenticate_request
    @current_user = AuthorizeRequest.call(request.headers).result
    render json: { error: 'Not Authorized' }, status: 401 unless @current_user
  end
end
```

---

By using `before_action`, the server passes the request headers (using the built-in object property request.headers) to <code class="green">AuthorizeRequest</code> every time the user makes a request. Calling result on <code class="green">AuthorizeRequest.call(request.headers)</code> is coming from SimpleCommand module where it is defined as attr_reader :result. The request results are returned to the <code class="green">@current_user</code>, thus becoming available to all controllers inheriting from ApplicationController.

---

## Testing

Open Postman and send a request to retrieve users like we did before. You should receive `unauthorized` error

---

![](https://i.imgur.com/Yvwexnn.png)

---


<code class="red">That's a lot to take in. Commit the changes and take a break.</code>

![Ruby water break](https://media2.giphy.com/media/3o6Ztd0zUjRtdKlk1q/giphy.gif?cid=ecf05e47h8fv2mwq8yzg8d7290uufc2xz6x5yb1rataqvair&rid=giphy.gif&ct=g)

---

# Routes

The pattern for routes is usually the
- `verb` - they specify an action to be performed on a specific resource or a collection of resources. 
- `path` - for example 'authenticate'

---

- `to` - this maps the request to a method. The value is divided into two. The first part is the name of the controller and the second part is the method, Therefore <code class="green">'home#test'</code> will go to home_controller and check for test method

To refer to a route, we use the path name. For example:

<code class="green">post 'home/test', to: home#test'</code> will be <code class="orange">home_test_path</code>

---

- `as` - Gives an alias to the path

When using `as`, you can define how you want to reference your path. For example: 

<code class="green">post 'branches/test', to: home#test', as: 'test'</code> will be <code class="orange">test_path</code> instead of <code class="orange">home_test_path</code>

---

- `resources` - This is used when you want to create routes for a resource. By default, Rails gives us access to the `CRUD` routes. You can decide the routes to keep by using keywords like:

    - `only` - this will only create routes for the defined methods. For example: 
<code class="green">resources 'users', only: 'index'</code> - this will only create a route for the index method in the user's controller.

    - `except` - this will create the CRUD endpoints except the ones you have specified. In API mode, Rails gives us
<code class="orange">index, create, show, update, delete</code>. In a Rails app, you have two more methods which are <code class="green">edit, new </code>

---

## Verbs

---

### post
- create a new resource. Modifies the database

---

### get 
- retrieves resource representation/information and does not modify it in any way.

---

### put
- update existing resource. POST requests are made on resource collections, whereas PUT requests are made on a single resource.

---

### patch
- make partial update on a resource. For example updating the password of a user without affecting the other attributes.

---

### delete
- delete resources (identified by the Request-URI. Usually the id of the resource to be deleted)

[Read more](https://restfulapi.net/http-methods/)

---

Let us add the authentication route


```ruby
Rails.application.routes.draw do
  post 'authenticate', to: 'authentication#authenticate'
  resources :messages
  resources :conversations
  resources :users
end
```

---

## Testing Authentication

Open Postman 

Enter [http://localhost:3000/authenticate](http://localhost:3000/authenticate) as the path

Choose `POST` from the verb drop down

Click on body and pick raw as the body format. Then on the drop down on your right, choose `JSON`

Paste this
```json=
{
    "username": "skywalker",
    "password": "princess123"
}
```
---

![](https://i.imgur.com/2pEDYVc.png)

---

Let us try with the correct password

Repeat the process above but replace the password with `passworD314` or the correct password for the user you're trying to authenticate.

This should return the auth token as shown below.

---

![](https://i.imgur.com/zcmkNjO.png)


---

## Authorizing Requests

To get rid of the `unauthorized` error we got before the break, we need to insert the token in the headers.

In your Postman, open the tab written headers.

Enter `Authorization` as the key and the auth_token result from authentication above as the value.

Note: Whenever you get unauthorized or expired token error, authenticate again and replace the authentication token with the new one.

---

![](https://i.imgur.com/aa6QUBq.png)

---

## Response Code

Visit [this link](https://dev.to/khaosdoctor/the-complete-guide-to-status-codes-for-meaningful-rest-apis-1-5c5) to learn about the HTTP status codes.

---

To get a list of all the routes in your application, run

```shell=
rails routes
```

For more details on routes, visit the [Rails documentation](https://guides.rubyonrails.org/routing.html)

---

It's time for another break! Shake! Shake! Shake!

![](https://media3.giphy.com/media/mFeEjqUVMfjpilVGS3/giphy.gif?cid=ecf05e477wx3t0b4wnql8gx7fs5wxyy40om9foa7w8rhejmb&rid=giphy.gif&ct=g)

---

# Controllers

---

Rails uses [Action Controller](https://guides.rubyonrails.org/action_controller_overview.html).

The controller will receive the request, fetch or save data from a model, and return an appropriate response to the user.

A controller can thus be thought of as a middleman between models and user. It makes the model data available to the user, and it saves or updates user data to the model.

---

## CRUD

APIs perform the following operations

- **C => Create** 
- **R => Read**
- **U => Update**
- **D => Delete**

---

In Rails logic, we have 5 main controller methods

<code class="green">create</code>
- This method creates a new record for the resource
- Verb: POST

---

<code class="blue">index</code>
- This method returns resource records
- Verb: GET

----

<code class="blue">show</code>
- This method returns an instance of a resource.
- Verb: GET

---

<code class="orange">update</code>
- This method performs an update on a record of the resource
- Verb: PUT/PATCH

---

<code class="red">delete</code>
- This method deletes a record from the database
- Verb: DELETE

---

<code class="blue">new</code>
- In a full-stack rails application, this method will render a template for collecting a new record for the resource. It is not applicable in an API
- Verb: GET

---

<code class="blue">edit</code>
- In a full-stack rails application, this method will render a template for editing a record for the resource. It is not applicable in an API
- Verb: GET

---

For our API, let's move our controller to api/v1 which will be our namespace.

We should also update the routes

<code class="green">config/routes.rb</code>
```ruby
Rails.application.routes.draw do
  post 'authenticate', to: 'authentication#authenticate'
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      resources :messages
      resources :conversations
      resources :users
    end
  end
end
```
Remember to add the namespace to your controllers as well.

---

## Users controller

---

We won't be returning all the users therefore we can remove the `index` method

```ruby
# app/controllers/api/v1/users_controller.rb

class Api::V1::UsersController < ApplicationController
  skip_before_action :authenticate_request, only: [:create]
  before_action :set_user, only: [:show, :update, :destroy]

  # GET /users/1
  def show
    render json: @user
  end

  # POST /users
  def create
    @user = User.new(user_params)

    if @user.save
      render json: @user, status: :created
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /users/1
  def update
    if @user.update(user_params)
      render json: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end

  # DELETE /users/1
  def destroy
    @user.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def user_params
      params.require(:user).permit(:username, :password, :password_confirmation, :avatar_url)
    end
end
```


---

We also need to update the routes to reflect this change.

<code class="green">config/routes.rb</code>

```ruby
Rails.application.routes.draw do
  post 'authenticate', to: 'authentication#authenticate'
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      resources :messages
      resources :conversations
      resources :users, only: [:create, :show, :update, :delete]
    end
  end
end
```

---

## Testing Route changes

To test the changes of anything outside the App folder, you will have to restart the server.

Stop the server by pressing `ctrl + c`

And start the server again by typing `rails server`

---

Open Postman

Enter [http://localhost:3000/users](http://localhost:3000/users) in the url field

Pick `GET` from the verb drop down.

Click `Send` and you should get a list of all the users.

You will get an error message because this path no longer exists.

---

![](https://i.imgur.com/RvTZbeV.png)

---

## Conversations Controller

We only want to display conversations that belong to a user. 

In the conversations controller, edit the calls to the model to only refer to the current_user's records.

For example, instead of <code class="red">Conversation.all</code>, we will have <code class="blue">@current_user.conversations</code>

---

Let us separate the conversations that the current user has contributed to and those that they have started.

Add a method called <code class="green">authored</code> method that will return the conversations that the current user has started. The index method will return all conversations that the current user has interacted with.

Let us also make sure the current user can only delete or update conversations that they have authored.

---

```ruby
class Api::V1::ConversationsController < ApplicationController
  before_action :set_conversation, only: [:show]
  before_action :set_authored_conversation, only: [:authored_conversation, :update, :destroy]

  # GET /conversations
  def index
    @conversations = @current_user.conversations

    render json: @conversations
  end

  # GET /conversations/authored
  def authored_conversations
    @authored_conversations = @current_user.authored_conversations

    render json: @authored_conversations
  end
  
  # GET /conversations/authored/1
  def authored_conversation
    render json: @authored_conversation
  end

  # GET /conversations/1
  def show
    render json: @conversation
  end

  # POST /conversations
  def create
    @authored_conversation = @current_user.authored_conversations.new

    if @authored_conversation.save
      render json: @authored_conversation, status: :created
    else
      render json: @authored_conversation.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /conversations/1
  def update
    if @authored_conversation.update(conversation_params)
      render json: @authored_conversation
    else
      render json: @authored_conversation.errors, status: :unprocessable_entity
    end
  end

  # DELETE /conversations/1
  def destroy
    @authored_conversation.destroy
  end

  private
    def set_conversation
      @conversation = @current_user.conversations.find(params[:id])
    end

    def set_authored_conversation
      @authored_conversation = @current_user.authored_conversations.find(params[:id])
    end
end
```

---

Add the new endpoints to <code class="green">routes.rb</code>

Restart the server and open Postman

Enter [http://localhost:3000/api/v1/conversations](http://localhost:3000/api/conversations) in the url field

You will get all the conversations the user has been part of. Remember if you get unauthorized, you have to authenticate the user and update the authorization token

---

![](https://i.imgur.com/KwOmBe2.png)


---

<code class="blue">To get the conversations the user has authored</code>

![](https://i.imgur.com/6QXffoB.png)

---

<code class="blue">To get a specific conversation that the user has authored</code>

![](https://i.imgur.com/WdsKZ1Z.png)

---

<code class="green">To initiate a conversation</code>

![](https://i.imgur.com/YRjK4aT.png)

---

# Serializer!

:8ball: :1234: To be continued ...