DEV Community

Jasmin Song
Jasmin Song

Posted on

Building the backend for a full stack web app

I made my second full stack project using React and Rails. It has been quite the journey. I implemented my knowledge of React, HTML, CSS, JavaScript for the frontend. For the backend, I utilized my new knowledge of Ruby on Rails. Today, we will focus on the backend.

During this journey, I learned about Rails fundamentals, CRUD with Rails, validations, error handling, Active Record associations, serialization, authorization, and authentication.

Whew. That's a lot. In this blog post, I'll walk you through each step I took to create my the backend of my full stack web app, Everything ELA. Everything ELA is a virtual space where students can read, comment, and post about English Language Arts (ELA) topics. It is a website for the students in my virtual school. Students are able to register, login, create posts, read posts, and log out. Comments are able to be created, read, updated, and deleted.

First, I made sure I had Ruby 2.7.4, NodeJS (v16), and npm set up. I cloned a project template repository and created a new remote repository on Github. I ran bundle install, rails db:create, and npm install.

At that point, I was all set up! This is where the fun started.

Models
I created my three models: Student, Comment, and Post and their tables.

create_table "comments", force: :cascade do |t|
    t.string "body"
    t.integer "post_id"
    t.integer "student_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
Enter fullscreen mode Exit fullscreen mode
create_table "posts", force: :cascade do |t|
    t.string "title"
    t.string "image"
    t.string "body"
    t.integer "student_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
Enter fullscreen mode Exit fullscreen mode
create_table "students", force: :cascade do |t|
    t.string "username"
    t.string "password_digest"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
Enter fullscreen mode Exit fullscreen mode

I knew I needed Active Record Associations so I planned that out.

class Comment < ApplicationRecord
  belongs_to :student
  belongs_to :post
end
Enter fullscreen mode Exit fullscreen mode
class Post < ApplicationRecord
  has_many :comments
  has_many :students, through: :comments
end
Enter fullscreen mode Exit fullscreen mode
class Student < ApplicationRecord
  has_many :comments
  has_many :posts, through: :comments
end
Enter fullscreen mode Exit fullscreen mode

Controllers
I then created my controllers using the rails g command. I knew that I wanted full CRUD capabilities for the Comments, create and read capabilities for the Students and Posts, and I needed a SessionsController for login and logout capabilities. I set up these methods in my controllers after creating them.

class CommentsController < ApplicationController

    def index

    end

    def create

    end

    def update

    end

    def destroy

    end

end
Enter fullscreen mode Exit fullscreen mode
class PostsController < ApplicationController
    def index

    end

    def show

    end

    def create

    end

end
Enter fullscreen mode Exit fullscreen mode
class SessionsController < ApplicationController

    def create

    end 

    def destroy

    end

end
Enter fullscreen mode Exit fullscreen mode
class StudentsController < ApplicationController

    def show

    end

    def create

    end

end
Enter fullscreen mode Exit fullscreen mode

Rails Routing
In the config/routes.rb file, I created my routes.

resources :comments
  resources :posts, only: [:index, :show, :create]
  post "/signup", to: "students#create"
  get "/me", to: "students#show"
  post "/login", to: "sessions#create"
  delete "/logout", to: "sessions#destroy"
Enter fullscreen mode Exit fullscreen mode

Validations
Now, onto validations. Validations are special method calls consist of code that performs the job of protecting the database from invalid data.

For example, I want to make sure that all comments are longer than 1 character, posts have a title, and students have unique usernames.

class Comment < ApplicationRecord
    validates :body, length: { minimum: 1 }

    belongs_to :student
    belongs_to :post
end
Enter fullscreen mode Exit fullscreen mode
class Post < ApplicationRecord
    validates :title, presence: true

    has_many :comments
    has_many :students, through: :comments
end
Enter fullscreen mode Exit fullscreen mode
class Student < ApplicationRecord

    has_many :comments, dependent: :destroy
    has_many :posts, through: :comments

    validates :username, presence: true, uniqueness: true
end
Enter fullscreen mode Exit fullscreen mode

Error Handling
I then updated the controller action to check the validity of our model when it is created, and respond appropriately:

class ApplicationController < ActionController::API
    include ActionController::Cookies

    rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
    rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response

    def render_unprocessable_entity_response(e)
        render json: {errors: e.record.errors.full_messages}, status: :unprocessable_entity
    end

    def render_not_found_response(invalid)
        render json: { errors: invalid }, status: :not_found
    end

end
Enter fullscreen mode Exit fullscreen mode

Serialization
Serialization is the process of translating data structures or objects into a format that can be stored or transmitted and reconstructed later. We need to send a standardized response to the API consumers to make sense of the transferred data. We need Active Model Serializers to structure the data we send back to the client appropriately. (In this case, JSON.)

I created serializers using the rails g command in the terminal. I then added the desired attributes and the necessary Active Record associations.

class CommentSerializer < ActiveModel::Serializer
  attributes :id, :body

  has_one :student
end
Enter fullscreen mode Exit fullscreen mode
class PostSerializer < ActiveModel::Serializer
 attributes :id, :title, :image, :body, :student_id
end
Enter fullscreen mode Exit fullscreen mode
class PostWithCommentsSerializer < ActiveModel::Serializer
  attributes :id, :title, :body

  has_many :comments
end
Enter fullscreen mode Exit fullscreen mode
class StudentSerializer < ActiveModel::Serializer
  attributes :id, :username
end
Enter fullscreen mode Exit fullscreen mode

Authentication
Authentication is how our application can confirm that our users are who they say they are. Simply put, we use usernames and passwords to verify our users. (In our case, students.)

class SessionsController < ApplicationController

    skip_before_action :authorize, only: :create

    def create
        student = Student.find_by(username: params[:username])

        if student&.authenticate(params[:password])
            session[:student_id] = student.id
            render json: student, status: :created
        else 
            render json: {errors: ["Invalid username or password"] }, status: :unauthorized
        end 
    end 

    def destroy
        session.delete :student_id
        head :no_content
    end

end
Enter fullscreen mode Exit fullscreen mode

Authorization
Authorization gives certain users permission to access specific resources. To authorize a student for specific actions, we can use the :student_id saved in the session hash. We can use a before_action filter to run some code that will check the :students_id in the session and only authorize students to run those actions if they are logged in.

And that's it! I used Rails fundamentals, CRUD with Rails, validations, error handling, Active Record associations, serialization, authorization, and authentication to build the backend of my web app.

Top comments (0)