How Non-PHP Web Apps Work

I’m currently teaching a web development immersive course at General Assembly and we’re on the section on MVC. We’re using Ruby with Sinatra to teach MVC and a pattern I’ve noticed is that students have a PHP style mindset about how routes are handled in a modular Sinatra app. By “PHP style mindset” I mean they seem to think that the file names have something to do with how routes are rendered. I want to cover how non-PHP web applications (including but not exclusively Sinatra) handle routing today so we can clear this up for beginners to Ruby, Node.js, Python, or other dynamic languages once and for all.

The PHP way

Before we talk about how things actually work let’s talk about how things work in PHP. Whether you know PHP or not this will likely make sense to you.

In PHP if we have a file structure that looks like this:

1
2
3
4
app
  |_index.php
  |_about.php
  |_contact.php

So that’s all. It’s a simple site with a homepage, about page, and contact form. In PHP your requests to mydomain.com/about.php are actually being sent directly to that file and that file runs. It’s just like a static website. This isn’t how it works in almost any other language.

In PHP we have the front controller pattern which comes close to emulating how things are done in other languages but otherwise this idea that the URL is somehow tied to a single file is wrong Wrong WRONG.

How it actually works

In the real world you have an application that is loaded into memory and runs. Since we’re teaching Sinatra I’ll use Ruby and Sinatra as an example but the same ideas apply to Rails, Django, Express, and other frameworks in other languages.

Let’s suppose we have that same website structure (homepage, about page, and contact form) but in a modular Sinatra app. Your directory structure will look a lot like this:

1
2
3
4
5
6
7
8
9
10
11
app
  |_controllers/
  | |_application.rb
  | |_abouts.rb
  | |_contacts.rb
  |
  |_models/
  | |_about.rb
  | |_contact.rb
  |
  |_config.ru

So I added some additional files here to make this more realistic. I added models for the about and contact page even though you’d likely not need them because about and contact are not resources but let’s forget this for a second.

How the app starts up

In Ruby we use a Rackup file to get Rack running as the glue between our Ruby code and our web server. Here’s our config.ru:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Require all of our app dependencies
require 'bundler'
Bundler.require :default, ENV['RACK_ENV'].to_sym # Load gems for the current environment plus all the defaults

# Now we require our models, helpers, libs, controllers, etc.
# except we'd usually use a bit of Ruby magic to make this easier.
# For this example I'm being very explicit so we can follow along easier.
# ---
# Require our models
require './models/about'
require './models/contact'

# Require controllers
require './controllers/application'
require './controllers/abouts'
require './controllers/contacts'

# Map our routes to our controllers (this is our router)
map('/')          { run ApplicationController }
map('/about')     { run AboutsController }
map('/contacts')  { run ContactsController }

Now if we call rackup the config.ru file is loaded up into memory and the mapping between URLs and controllers is set.

What is a require

When we require a file, yes, we do require the file’s contents but the more important part of this is that the name of the file and its contents should be separated in our minds. When we require './controllers/about' we are loading a class called AboutsClass.

How require and map work together

When we type in localhost:9292/about into our browser we’re not reading the contents of the controllers/abouts.rb file and executing them. What’s really happening is that we have a Rack application loaded into computer memory (RAM) and it is currently running. That program has a link between the mapping of the URL /about to the controller class named AboutsController. The program knows about this class because we used require to load its code into memory. Loading something into memory doesn’t mean it runs right away. There’s just a mapping between the URL and the class that is remembered and the Rack app (your app, the one running at localhost:9292) is then calling that class and passing information to it like the full URL that was called along with whatever else was in the ENV hash at that time.

That class then runs whatever internal method it needs in order to satisfy the request.

No file is ever called directly. config.ru simply loads up any classes you require and then waits for a URL so it can route it to a controller class.

Let’s pretend for a moment that I named the file containing the AboutsController kittens.rb and then called require ./controllers/kittens.rb in my config.ru file. The application would still respond the exact same way. Going to localhost:9292/about will still run the AboutsController despite the file name being called kittens.rb.

Conclusion

Please separate file names from class names. Files are simply containers for saving code. Requiring them simply loads variables, classes, and objects into memory. Ruby, Node, and other languages are not like PHP where URLs are tied to files. Files are never directly accessed when you run a Ruby web application online.

Objects are instantiated, methods are invoked, variables are declared, but controller files are never accessed and run in the way they are in PHP.

Bonus: What’s a Model?

People get tripped up when they see model and controller files with similar names and similar class definitions. Classes are the glue between models and views. A Controller will render a view and fill in any data it needs (if it needs it).

Models are a representation of database data in code. So for a user database table your model may look like this:

1
2
3
4
5
6
7
8
9
10
11
class User
  require 'securerandom' # lets us generate unique IDs
  attr_accessor :id, username, password, email

  def initialize(id, username, password, email)
    @id = id || SecureRandom.uuid
    @username = username || nil
    @password = BCrypt::Password.create(password) # we use BCrypt to hash passwords ALWAYS
    @email    = email || nil
  end
end

This model represents the data we have in the database but there are no methods to actually connect it up to a database and perform CRUD actions through it. That’s why we use an ORM like ActiveRecord or, my personal favorite, the Sequel Gem which follows the Active Record pattern.

Getting Database Connections and CRUD Hooks for Free

If we were to use the Sequel Gem (or another ORM) then our User model would look like this:

1
2
class User < Sequel::Model
end

Now as long as you name your database table the same name as your class name (User) then you’ll not need to write any getters, setters, or other code. You’ll automatically be able to perform CRUD actions on your model in your controllers using familiar methods like User.create(params).

All together now

  1. Your router will map a URL to a controller
  2. Your controller will invoke a function to render a response. Your controller may use a Model to get data or perform CRUD actions before sending that response to the view
  3. Your view, having gotten any needed data from a controller, will then be sent back out to the client (usually a web browser but it could be an API client)

That’s it

And that’s all there is to it. Model View Controller isn’t hard. What’s hard is having a very simple application that’s broken out into all these different model, view, controller pieces and not understanding why it has to be so complicated. This complexity in simple apps likely ends up having students thinking that somehow the file names are important and the files themselves must be being called. Otherwise why would we have a minimum of seven files for a site with 3 pages? When they begin to create more complex apps they’ll understand it.

What’s important now though is to stop thinking about files. Just remember the conventions (models are singular, controllers are plural) for files and that’s it. The important thing to remember is that you’ve got a program loaded into memory that runs when a URL you’ve mapped to a class is called.

Your turn

Do you have a good or favorite way to explain MVC to students learning web development and MVC? What is it? Please leave a comment or get in touch with me as it would really help me out.

Web development

« Securing API Keys in a JavaScript Single Page App Training Wheels »

Comments