Sinatra Web App: MVC, Sessions, and Routes

jacquelinelam profile image jacqueline-lam Updated on ・4 min read

A Sinatra Web App for Boulderers

My Sinatra-powered web app — Bolderer, is designed for boulderers who are eager to track their climbing progress, strengths and weaknesses by logging routes/ problems that they have climbed. They can also see others' profiles since bouldering is a personal yet social sport, where the climbing community is supportive and would always cheer for your 'sends'.

I started my project by mapping out the models and their relationships with one another. This helped me plan the models, database tables, and the has_many, belongs_to and many-to-many relationships on the models.

Bolderer Association Diagram:

Bolderer Association Diagram

Key Concepts

I would like to highlight some key building blocks of my Sinatra project — MVC, Sessions, CRUD Actions, and RESTful Routes, which have allowed me to create a functional app and taught me the base concepts of a Content Management System (CMS).

Model-View-Controller (MVC)

An organized way to build frameworks for web apps.

I found this immensely useful in helping me structure my code. It provided a separation of concerns by grouping my files by functionalities -- Models (logic, where data is manipulated and saved), Views (front-end), and Controllers (middleman - relays data from browser to app, and from app to the browser).

My MVC directories:


User Password Encryption with BCrypt

I used the Ruby Gem BCrypt to secure the users' data by encrypting their passwords. It allowed my app to sign up and log-in a user with a secure password.

Implementing Bcrypt by adding the password column as pasword_digest in my users table:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :username
      t.string :password_digest

I also added ActiveRecord's macro has_secure_password to my User model, which let me access the user's password attribute even though the users database only had the column called password_digest.

The macro also provided another useful method, authenticate:

post '/login' do
    user = User.find_by(username: params[:username])

    if user && user.authenticate(params[:password])
      #storing user_id key in session hash
      session[:user_id] = user.id
      redirect "/users/#{user.id}"
      @error = true
      erb :"/users/login"

The User's authenticate method turned the user's password input into a hash that would get compared with the hashed password stored in the database. If the user authenticated, the method would return the User instance; otherwise, it would return false. This was helpful for logging in users.

Sessions and User Authentication and Authorization

  • Sessions were used to verify a user's identity, as the users were requested to verify their identity by logging in with valid credentials
  • Hence, I used sessions to filter and dictate what a user could see or edit:

    • The users could only view the Problems index page if they were logged in
    • The users could only edit/ delete the resources (problems) that they created
  • To use sessions in Sinatra, I enabled my application to use the sessions keyword to access the session hash:

  configure do
    set :public_folder, 'public'
    set :views, 'app/views'
    enable :sessions
    set :session_secret, "session_encryption"
  • When a user signs in successfuly, the user_id key would get stored in the session hash: session[:user_id] = user.id.

Defining RESTful routes

Another key lesson was learning to use restful routes to implement CRUD actions. I realized how important it was to plan my routes in order to handle the HTTP verbs and URLs in an organized and standardized way.

I designed my ProblemsController, UsersController and SessionsController controller actions to follow restful conventions and to map the HTTP verbs (get, post, patch, delete) to the controller CRUD actions.

For my app, I wanted a logged-in user to be able to access all CRUD actions: the ability to create sessions (login)/ problems, read, update or delete their own problems.

My Problems Controller:

HTTP Verb Route CRUD Action Used for/ result
GET / index index page to welcome user - login/ signup
GET /problems index displays all problem (all problems are rendered)
POST /problems create creates a problem; save to db
GET /problems/:id show displays one problem based on ID in the url
(just one problem is rendered)
GET /problems/:id/edit edit displays edit form based on ID in the URL
PATCH /problems/:id update modifies an existing problem based on ID in the url
DELETE /problems/:id delete deletes one article based on ID in the URL

My Users Controller and Sessions Controller:

HTTP Verb Route CRUD Action Used for/ result
GET /signup index display signup form
POST /signup create creates a user
GET /login index displays login form
POST /login create create a session
GET /logout delete delete session/ log out
GET /users/:username show display one user’s problems based on :username in the url

Other resources:

  • Seeding Data: The dummy data was very important as it helped to demonstrate to the first user(s) how this web app could be used. There was the option of using APIs to seed data (especially for general topics like recipes), but I decided to create my own instances of Users, Problems, and Styles and their associations to make my data more personal (as it reflected my actual bouldering logs)
  • Bootstrap: This allowed me to add styling to my forms, buttons, etc. without having to build the CSS from scratch

Posted on Mar 14 by:

jacquelinelam profile



Full-Stack Software Engineer, with a background in Digital Marketing.


markdown guide