Opinionated Framework Part 1 - Setup and Routing
What you will learn
- What is an Opinionated Framework?
- How to start up a new project
- How to create a controller and actions
- How to connect controller actions to routes
Prerequisites
- Ruby 3 or later (installed with rbenv ideally)
- Rails 6.1.3 or later
- A Text Editor (like vscode)
- Postgres 12 or higher installed
if you have issues installing the pg Gem, make sure you have Xcode installed on mac or libpq-dev installed on linux, sudo apt-get install libpq-dev
on any ubuntu based system
What is an opinionated framework?
Most web frameworks for building full-stack websites or APIs/Microservices fall into one of two categories.
- Minimalist: Like express, these frameworks provide you the basic features to create a web server leaving it up to you to decide which libraries to use for connecting to databases, encryption, authentication, etc. and how to structure your files and folders in your workflow.
- Opinionated: These frameworks usually come with CLI tools that allow you to spin-up new projects with pre-defined projects structures, conventions and all the libraries for full functionality already built in.
These are the main web frameworks across many languages:
Language | Minimalist | Opinionated |
---|---|---|
Javascript | Express, Koa | Nest, Foal, Sails |
Python | Flash, FastAPI | Django, Masonite |
Ruby | Sinatra | Ruby on Rails |
PHP | Slim | Laravel |
GO | Echo | Buffalo, Revel |
Rust | Warp | Rocket |
Java | Spring Boot | |
C# | .Net | |
Dart | Aqueduct, Angel | |
Kotlin | kTor, Javalin | Spring Boot |
Swift | Vapor, Kitura |
Setting Up the Project
The rails new
command allows us to generate new products. For our purposes we will be using several flags to create an API with postgres as our database.
rails new firstrails --api -d postgresql
--api
Tells rails to generate a project using a template optimized for RESTful APIs vs its default Full Stack Template.-d postgresql
tell rails to setup the database settings to be ready for using postgres as the database.
Testing The Dev Server
- cd into the new firstrails folder and run the development server with the command
rails server
, go to localhost:3000 in the browser and you'll find a database error, its because our database doesn't exist yet.
Creating Our Database
If your on a mac and installed postgres using postgres.app shouldn't have to change any of the database settings for development. If you are one Linux and maybe windows you may have to specify your local database credentials for the development environment in config/database.yml
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
## Add the lines below to connect your local database
host: localhost
port: 5432
username: test5 #use the username for your local environment
password: test5 #use the password for your local environment
development:
<<: *default
database: firstrails_development
If planning to deploy a project to heroku you should change the production settings to look like so in the database.yml before deploying.
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
Now that the settings are in database.yml rails can create our database for us by running the command rails db:create
. Try your dev server again and you should see the default rails page.
Creating a Controller
A controller is a class that will house functions that will determine what kind of response we will send the user. We can generate a new controller with the following command.
rails g controller first
This will create the file app/controllers/first_controller.rb
class FirstController < ApplicationController
end
Notice it inherits from ApplicationController, which if you look at application_controller.rb inherits from ActiveController.
- ActiveController -> The base class in rails that give controllers all their methods
- ApplicationController -> is the middle controller that can be used to pass functionality to multiple controllers via inheritance
- YourController -> All the controllers you create which inherit from ApplicationController, the functions in these classes determine the responses to server request.
Writing an Action
Create the following function in FirstController
class FirstController < ApplicationController
def first
render json: {hello: "world"}
end
end
Every controller function must end by invoking the render function which sends the response. The function can then receive a key of the type of data you are sending (json:, text:, xml:) followed by the data to be sent.
These functions are referred to as actions, every controller is made up of several actions.
Creating a Route
Routes determine which URLs point to which actions, these are tracked in config/routes.rb. Let's add a route for our "first" action.
Rails.application.routes.draw do
get "/", to: "first#first"
end
notice the pattern
verb "/endpoint", to: "controller#action"
Pretty straightforward, right!
Using URL Params
Let's create a route that uses a URL param:
Rails.application.routes.draw do
get "/", to: "first#first"
get "/route/:myparam", to: "first#second"
end
Now let's add the matching action to our controller!
class FirstController < ApplicationController
def first
render json: {hello: "world"}
end
def second
render json: {myparam: params["myparam"]}
end
end
Head over to the browser and visit, "/route/itworks!" and you'll that the variable part of the route ":myparam" was captured inside the params object.
Accessing URL Queries
The params object also captures url queries, modify the second function like so!
def second
render json: {myparam: params["myparam"], myquery: params["query"]}
end
Now visit "/route/itworks!?query=stillworking" in the browser and see that you successfully captured the query. Nice!
Accessing the Request Body
The params object houses everything in rails, the params, the queries and the request body. Let's create a new route...
Rails.application.routes.draw do
get "/", to: "first#first"
get "/route/:myparam", to: "first#second"
post "/route/:myparam", to: "first#third"
end
then create the matching action
def third
render json: params.to_json
end
send a post request to "localhost:3000/route/another?here=somemore" and including some data in the body and look at the response to see how rails packages the params object.
Notice that it includes all the body properties individually but also packages in a sub-object named after the controller.