Ruby-setup

  • Create a new application

    rails new . --database postgresql

  • Update the postgres user and password in config/database.yml:

 username: postgres
password: Gracewanjiru1573

  • Create the database

    rails db:create

  • Create a scaffold for the database tables

    rails g scaffold table_name

  • Create the routes for the application

  • Create the FKs for the tables in the migration files

    rails g migration AddColumnToTable table:references

  • Make sure you create the tables first before creating the FKs

  • Run the migration files

    rails db:migrate

  • Create the associations in the models.

  • Go to the routes and create the namespaced routes:

  namespace :api do
  namespace :v1 do
  resources :table_name
  end
  end

  • Move the controllers to the api/v1 folder

  • Add cors rb gem to the gemfile gem 'rack-cors'

  • Run bundle install

  • Create a file in the config/initializers/cors.rb


  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

  • Add the following gems

gem "bcrypt"
gem "rack-cors"
gem "active_model_serializers"
gem "jwt"

  • Install the gems bundle install

  • In the config/initializers/cors.rb file add the following:


  Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
  origins '\*',
  resource '\*',
  headers: :any,
  methods: %i[get post put patch delete options head]

  end
  end

  • Create the user model with the following attributes rails g model User name email password_digest

  • Create the user serializer to serialize our api and choose what we want displayed on our endpoints rails g serializer User

  • Create the user controller rails g controller api/v1/Users

    • Migrate the database rails db:migrate

    • We will need to use the has_secure_password macros to make sure that the password is hashed using the bcrypt gem . Add has_secure_password to app/models/user.rb.

    • First , we code a create action in the users controller that will allow for signing up .The create action will be used to create a new user. Add this to the user's controller in api/v1/users_controller.rb


def create
 user = User.new(user_params)
 if user.save
 render json: user
  else render json: {error: "Error creating user"}
  end
   end

private
 def user_params
 params.require(:user).permit(:username, :email, :password)
  end

  • In the user serializer , change the attributes to be displayed . So you can only see the email and the username . Add this to the user serializer in app/serializers/user_serializer.rb

class UserSerializer < ActiveModel::Serializer
 attributes  :username, :email
 end

  • Finally , add a route for our creat action , This will be done in the config/routes.rb . Add this code inside the routes.rb file
Rails.application.routes.draw do
 namespace :api do
 namespace :v1 do
 resources :users, only: [:create]
 end
 end
end


{
"user": {
"username": "test",
"email": "test@gmail.com
"password": "test"
}
}

  • You should get a response with the user's email and username.

{
"username": "test",
"email": "test@gmail.com"
}

  • To add validation to the email so that a user cannot sign up with an invalid email address , add the following to the user model in app/models/user.rb

validates :email, presence: true, uniqueness: true

  • To add validation to the password so that a user cannot sign up with a password that is less than 6 characters , add the following to the user model in app/models/user.rb
validates :password, length: {minimum: 6}

  • Next, create a token for the user when they sign up. Use the jwt gem to create the token. To do this , add an encode_token method to the application controller
  def encode_token(payload)
    JWT.encode(payload, 'my_s3cr3t')
  end

We now modify our create action to give us a token everytime we sign up a user


  def create
  user = User.new(user_params)
  if user.save
    token = encode_token({ user_id: user.id })
    render json: { user: UserSerializer.new(user), jwt: token }
  else
    render json: { error: 'Error creating user' }
  end
end

  • Try signing up a user again and you should get a token in the response

{
"user": {
"username": "test2",
"email":test2@gmail.com",
"password": "password2"
}
}

  • The output should be

{ "user":
{ "username": "test2",
"email": "test2@gmail.com"
},
 "token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0fQ.oFQeWUjCQx5X2vxlAH_bzAKijAkSbiS2hW9-EQS883o"
 }

  • This token will be used to authenticate the user when they try to access protected routes.

  • To get the token from the header in the users controller, so as to get access to the current user, add the following method to the application controller.To get the current user, we will need to create a helper method in the application controller. This method will decode the token and get the user id from it. It will then use the user id to find the user in the database and return the user.


def decoded_token
    if auth_header
        token = auth_header.split(' ')[1]
        begin
            JWT.decode(token, 'my_s3cr3t', true, algorithm: 'HS256')
        rescue
            nil
        end
    end

end

def current_user
    if decoded_token
        user_id = decoded_token[0]['user_id']
        @user = User.find_by(id: user_id)
    end
end

def logged_in?
    !!current_user
end

def authorized
    if logged_in?
        true
    else
        render json: { message: 'Please log in' }, status: :unauthorized
    end
end

  • With this , create an action and a route that will provide details on the logged in /signed up current user . This is where the jwt token comes in to play . First , in our users controller create a profile method that will return a user.

def profile
    render json: { user: UserSerializer.new(current_user) }, status: :accepted
end

  • Next , in our routes.rb file , add a route for the profile action

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:create]
      get '/profile', to: 'users#profile'
    end
  end
end

  • Also add this line to the top of the users controller:

 skip_before_action :authorized, only: [:create]

  • The users controller should now look like this:

class Api::V1::UsersController < ApplicationController
  skip_before_action :authorized, only: [:create]
  def create
    user = User.new(user_params)
    if user.save
      token = encode_token({ user_id: user.id })
      render json: { user: UserSerializer.new(user), jwt: token }
    else
      render json: { error: 'Error creating user' }
    end
  end

  def profile
    render json: { user: UserSerializer.new(current_user) }, status: :accepted
  end

  private

  def user_params
    params.require(:user).permit(:username, :email, :password)
  end
end

  • For the jwt token to get the current user . Lets get this token from the response header after signing up and use it in the header of the GET request to the /api/v1/profile endpoint to get the current user . First, let us sign up a new user , send a POST REQUEST to the endpoint below

http://localhost:3000/api/v1/users

  • With the following data in the body

{
"user": {
"username": "test3",
"email": "test3@gmail.com",
"password": "password3"
}
}

  • You should get a response with the user's email and username and a token

{
"user": {
"username": "test3",
"email": "test3@gmail.com"
},
"token" : ewiho2039103j3ijeohoh1oej019u0192jj0is1js01j
`}

  • Copy the token and use it in the header of the GET request to the /api/v1/profile endpoint to get the current user . In postman , send a GET request to the endpoint below

http://localhost:3000/api/v1/profile

  • On the headers tab , add a key of Authorization and a value of Bearer . The token is the one you copied from the response of the POST request to the /api/v1/users endpoint.

  • On the headers for the request add one for Authorization , and add the keyword Bearer , followed by the token you got upon signing up The Authorization Header should look like this Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1fQ.Oo2CYiJr2SrNc8DVm_DNnMHzTm0j4ACjzgs5JqogTQ

  • We will get this as the response body


{ "user":
 { "username": "test3",
  "email": "test3@gmail.com" }
  }

  • Create the actions and routes for the login and logout endpoints. Lets create a auth controller and add the following actions to it

def create
    @user = User.find_by(username: login_params[:username])
    if @user && @user.authenticate(login_params[:password])
        token = encode_token(user_id: @user.id)
        render json: { user: UserSerializer.new(@user), jwt: token }, status: :accepted
    else
        render json: { error: 'Invalid username or password' }, status: :unauthorized
    end
end

private

def login_params
    params.require(:user).permit(:username, :password)
end

  • Modify the routes.rb file to add the login route

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:create]
      post '/login', to: 'auth#create'
      get '/profile', to: 'users#profile'
    end
  end
end

  • Run the rails server and send a POST request to the endpoint below

http://localhost:3000/api/v1/login

  • With the following data in the body

{
"user": {
"username": "test3",
"password": "password3"
}
}

  • You should get a response with the user's email and username and a token

{

"user": {
"username": "test3",
"email": "test3@gmail.com"
},
"jwt" : ewiho2039103j3ijeohoh1oej019u0192jj0is1js01j
`}

  • This profile can now be used to get the current user. To logout a user, we will need to delete the token. To do this, we will need to create a destroy action in the auth controller

def destroy
    session.delete(:user_id)
    render json: { message: 'Logged Out' }
end

  • Modify the routes.rb file to add the logout route

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: [:create]
      post '/login', to: 'auth#create'
      delete '/logout', to: 'auth#destroy'
      get '/profile', to: 'users#profile'
    end
  end
end

  • Run the rails server and send a DELETE request to the endpoint below

http://localhost:3000/api/v1/logout

  • You should get a response with the message Logged Out

{

"message": "Logged Out"

}

  • Make sure you add this line to the controllers for them to skip the authorization
skip_before_action :authorized

  • Also make sure the you render the json when creating the index method in the controllers

  • Remember to add the seed data to the seeds.rb file and run the command rails db:seed to seed the database with the data.

  • This is the end of the backend part.