---
title: API Development
tags: API, Ruby, Ruby on Rails
description: Step by step API development with Ruby on Rails.
---
[](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>

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

---
<code class="orange">You have successfully made an API!! 🥳 </code>
<code class="blue">Take a break. Stretch. Drink some water.</code>

---
# 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
---

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

---
# 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"
}
```
---

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

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

---
## 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!

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

---
## 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
---

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

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

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

---
# Serializer!
:8ball: :1234: To be continued ...