Building the backend for a full stack web app
Tags: rails, ruby, react, javascript
Published: 2023-02-26 01:59:51

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

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

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

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


```
class Comment < ApplicationRecord
  belongs_to :student
  belongs_to :post
end
```

```
class Post < ApplicationRecord
  has_many :comments
  has_many :students, through: :comments
end
```

```
class Student < ApplicationRecord
  has_many :comments
  has_many :posts, through: :comments
end
```

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

```
class PostsController < ApplicationController
    def index

    end

    def show

    end

    def create

    end

end
```

```
class SessionsController < ApplicationController

    def create
     
    end 

    def destroy

    end

end
```

```
class StudentsController < ApplicationController

    def show

    end

    def create

    end

end
```

**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"
```

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

```
class Post < ApplicationRecord
    validates :title, presence: true
    
    has_many :comments
    has_many :students, through: :comments
end
```

```
class Student < ApplicationRecord

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

    validates :username, presence: true, uniqueness: true
end
```

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

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

```
class PostSerializer < ActiveModel::Serializer
 attributes :id, :title, :image, :body, :student_id
end
```

```
class PostWithCommentsSerializer < ActiveModel::Serializer
  attributes :id, :title, :body

  has_many :comments
end
```

```
class StudentSerializer < ActiveModel::Serializer
  attributes :id, :username
end
```

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

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