Web development 101

Henning Koch
Head of Development at makandra
@triskweline

Web applications?

Native apps vs. web apps

Native applications

Entire app runs on a single computer

Entire app is (often) written in a single language
Popular languages: Java, C#, C++, Objective-C

Web applications

Application runs on multiple computers
Typical: One server, many clients (= web browsers)

Client and server run different parts of the application
and they communicate over HTTP

Application is written in a "stack" of many technologies
Typical stack: HTML, CSS, JavaScript, Ruby/Node/Java/C#, SQL

Language Purpose Runs where?
HTML Textual content Client
CSS Layout & Design Client
JavaScript Client-side interaction Client
HTTP Client/server communication Client/server
Ruby/Node/Java/C# Controller & Model Server
SQL Database access Server

In 2024 web apps are the dominant form of user-facing applications

  • Can be used from any device with a web browser
    (Windows, Linux, macOS, Android, iOS, ...)
  • No installation
    Web browser downloads and "installs" a fresh copy every time you visit a website
  • Is always the latest version
  • Inherently multi-user
  • Gets better every year (vs. native apps)
    File access, offline, audio processing, 3D graphics, ...

Today

  • HTML & HTTP
  • CSS
  • Layout with Flexbox
  • Break
  • Server-side web apps
  • JavaScript
  • Client-side web apps (SPAs)
  • Discussion / end


Read along

http://webdev101.makandra.de
https://github.com/makandra/webdev101

HTML
Hypertext Markup Language

HTTP
Hypertext Transfer Protocol

HTML

<html>
  <body>
    <h1>Hi there!</h1>
    <p>Look at this image:</p>
    <img src="image.jpg">
  </body>
</html>

View example
(use the inspector)

HTTP

User enters into her browser's address bar:

http://makandra.com/page.html

What happens?

Browser asks server:

GET /page.html HTTP/1.1        # I want the file /page.html
Host: makandra.com             # I want it from makandra.com
Accept: text/html, text/plain  # I understand HTML and plain text

Server looks for a local file page.html and replies:

HTTP/1.1 200 OK                # I found what you wanted
Content-Length: 114            # What you want has 114 bytes
Content-Type: text/html        # And it's in HTML format
                               # Here it comes:
<html>
  <body>
    <h1>Hi there!</h1>
    <p>Look at this image:</p>
    <img src="image.jpg">
  </body>
</html>

The browser parses the HTML and encounters this tag:

<img src="image.jpg">

What happens?

Browser asks server:

GET /image.jpg HTTP/1.1        # I want the file /image.jpg
Host: makandra.com             # I want it from makandra.com
Accept: image/jpeg, image/png  # I understand JPEG and PNG

Server looks for a local file image.jpg and replies:

HTTP/1.1 200 OK                # I found what you wanted
Content-Length: 67840          # What you want has 66 KB
Content-Type: image/jpeg       # And it's in JPEG format
                               # Here it comes:
FF D8 FF E0 00 10 4A 46
49 46 00 01 01 01 00 48
00 48 00 00 FF E1 1C 13
45 78 69 66 00 00 49 49
2A 00 08 00 00 00 0D 00
0F 01 02 00 06 00 00 00
...

HTML

CSS
Cascading Style Sheets

HTML

<html>
  <body>
    <h1>About Capybaras</h1>
    <p>
      The capybara is the
      largest rodent
      in the world.
    </p>
  </body>
</html>
 
 

Browser

HTML

<html>
  <body>
    <h1>About Capybaras</h1>
    <p>
      The capybara is the
      largest rodent
      in the world.
    </p>
  </body>
</html>
 
 

CSS

body {
  font-family: 'Arial';
}

h1 {
  text-transform: uppercase;
  font-style: italic;
}

b {
  background-color: #ff9900;
}

View both together

Colors in CSS

#ff0000
#00ff00
#0000ff
#ffff00
#ffffff
#000000
#ff9900
#4488bb
#9900ff
#777777
#22aa88
#885555
red
yellow
fuchsia
rebeccapurple
rgba(255, 0, 0, 1.0)
rgba(255, 0, 0, 0.75)
rgba(255, 0, 0, 0.5)
rgba(255, 0, 0, 0.25)
hsl(0, 100%, 50%)
hsl(30, 100%, 50%)
hsl(30, 50%, 50%)
hsl(30, 50%, 70%)

How to link HTML and CSS (1)

<html>
  <head>
    <style>
      h1 {
        color: #ff0000;
      }
    </style>
  </head>
  <body>
    ...
  </body>
</html>

How to link HTML and CSS (2)

<html>
  <head>
    <link rel="stylesheet" href="styles.css">




  </head>
  <body>
    ...
  </body>
</html>

The browser parses the HTML and encounters this tag:

<link rel="stylesheet" href="styles.css">

What happens?

Browser asks server:

GET /styles.css HTTP/1.1       # I want the file /styles.css
Host: makandra.com             # I know you as makandra.com
Accept: text/css               # I understand the CSS format

Server looks for a local file styles.css and replies:

HTTP/1.1 200 OK                # I found what you wanted
Content-Length: 182            # What you want has 182 bytes
Content-Type: text/css         # And it's in CSS format
                               # Here it comes:
body {
  font-family: 'Arial';
}

h1 {
  text-transform: uppercase;
  font-style: italic;
}

...

DevTools Network Tab

An advanced CSS example

View example

Selectors

body {
  font-family: 'Arial';
}

h1 {
  text-transform: uppercase;
  font-style: italic;
}

b {
  background-color: #ff0000;
}

Selectors

body {
  font-family: 'Arial';
}

h1 {
  text-transform: uppercase;
  font-style: italic;
}

b {
  background-color: #ff0000;
}

 


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

h1 { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

p { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

p.introduction { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

p:last-child { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

h1, p:last-child { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

h1+p { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

b { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

p.introduction b { ... }


<h1>About Capybaras</h1>

<p class="introduction">
  The capybara is the largest <b>rodent</b>
  in the world.
</p>

<p>
  The capybara belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>
(Time check)

The box model

View example

Layout with Flexbox

View example

Block elements stack


.container {
  display: block; /* default */
}
  

Flexbox children are columns


.container {
  display: flex;
}
  

justify-content


.container {
  display: flex;
  justify-content: flex-start; /* default */
}
  

justify-content


.container {
  display: flex;
  justify-content: flex-end;
}
  

justify-content


.container {
  display: flex;
  justify-content: space-between;
}
  

justify-content


.container {
  display: flex;
  justify-content: space-around;
}
  

justify-content


.container {
  display: flex;
  justify-content: center;
}
  

Pushing siblings


.container {
  display: flex;
}
.child:nth-child(1) {
  margin-right: auto;
}
  

flex-grow


.container {
  display: flex;
}
.child {
  flex-grow: 1;
}
  

flex-grow


.container {
  display: flex;
}
.child:nth-child(2) {
  flex-grow: 1;
}
  

flex-grow


.container {
  display: flex;
}
.child:nth-child(1) {
  flex-grow: 1;
}
.child:nth-child(2) {
  flex-grow: 2;
}
  

flex-basis


.container {
  display: flex;
}
.child {
  flex-basis: 200px;
}
  

flex-basis


.container {
  display: flex;
}
.child {
  flex-basis: 200px;
}
.child:nth-child(2) {
  flex-grow: 1;
}
  

Many elements


.container {
  display: flex;
}
  

Many elements


.container {
  display: flex;
}
.child {
  flex-shrink: 0;
}
  

Many elements


.container {
  display: flex;
  flex-wrap: wrap;
}
  

Many elements


.container {
  display: flex;
  flex-wrap: wrap;
}
.child {
  flex-basis: 25%;
}
  

Many elements


.container {
  display: flex;
  flex-wrap: wrap;
}
.child {
  flex-basis: 25%;
  flex-grow: 1;
}
  

flex-direction


.container {
  display: flex;
  flex-direction: row;
}
  

flex-direction


.container {
  display: flex;
  flex-direction: row-reverse;
}
  

flex-direction


.container {
  display: flex;
  flex-direction: column;
}
  

flex-direction


.container {
  display: flex;
  flex-direction: column-reverse;
}
  

align-items


.container {
  display: flex;
  height: 200px;
  align-items: stretch; /* default */
}
  

align-items


.container {
  display: flex;
  height: 200px;
  align-items: flex-start;
}
  

align-items


.container {
  display: flex;
  height: 200px;
  align-items: flex-end;
}
  

align-items


.container {
  display: flex;
  height: 200px;
  align-items: center;
}
  

align-items


.container {
  display: flex;
  height: 200px;
  align-items: center;
}
.child:nth-child(2) {
  align-self: stretch;
}
  

align-items


.container {
  display: flex;
  height: 200px;
  align-items: center;
  justify-content: center;
}
  

Flexbox resources

CSS frameworks

Instead of starting from scratch you can build upon someone else's CSS.
CSS frameworks like Bootstrap, Bulma or Primer give you:

  • Normalized base styles
  • Typography
  • Grid system
  • Form styles
  • Components (cards, modals, tabs, etc.)
  • Utility classes

View example

More CSS resources and exercises

After the break

  • Server-side web apps
  • JavaScript
  • Client-side web apps

Server-side
Web applications

Let's write a web app that returns the current time:

http://timeapp.com/now.html

Browser asks server:

GET /now.html HTTP/1.1         # I want the file /now.html
Host: timeapp.com              # I want it from timeapp.com
Accept: text/html, text/plain  # I understand HTML and plain text

Server looks for a local file now.html and replies:

HTTP/1.1 200 OK                # I found what you wanted
Content-Length: 22             # What you want has 22 bytes
Content-Type: text/plain       # And it's in plain text
                               # Here it comes:
The time is 15:00:00.

 
 
 

Browser asks server:

GET /now.html HTTP/1.1         # I want the file /now.html
Host: timeapp.com              # I want it from timeapp.com
Accept: text/html, text/plain  # I understand HTML and plain text

Server runs a program time.exe.
The program time.exe writes a HTTP response
with the current time to STDOUT:

puts "HTTP/1.1 200 OK"
puts "Content-Length: 22"
puts "Content-Type: text/plain"
puts
puts "The time is " + Time.now.to_s

Browser don't know about web apps!

The browser simply makes a request and gets back a string of HTML (wrapped in HTTP).

The browser doesn't even know if the HTML was generated by an app,
or if it was simply read from a static file.

You can write web apps in any language that can print text

Popular choices:

  • Node.js
  • Ruby
  • Java
  • C#
  • PHP
  • Python

The following examples are in Ruby, but concepts apply to all languages.

Ruby

class User

  def initialize(name, age)
    @name = name
    @age = age
  end

  def adult?
    @age >= 18
  end

end

user = User.new('Max', 18)
puts user.name
puts user.adult?

Java

class User {

  private String name;
  private int age;

  public User(name, age) {
    this.name = name;
    this.age = age;
  }

  public boolean isAdult() {
    return age >= 18;
  }

}

var user = new User("Max", 18);
System.out.println(user.getName());
System.out.println(user.isAdult());

The chat app

To run the chat app:

cd examples/2000_chat
bundle install
bundle exec ruby app.rb

You can now access the app at http://localhost:4567/

The user types into her address bar:

http://chatapp.com/

What happens?

Browser asks server:

GET / HTTP/1.1                 # I want /
Host: chatapp.com              # I want it from chatapp.com
Accept: text/html, text/plain  # I understand HTML and plain text

Server asks the program app.rb for /.

The program prints a new HTML page with the latest chat messages.

The server takes the program output and sends it the browser:

HTTP/1.1 200 OK                # I found what you wanted
Content-Length: 1589           # What you want has 1589 bytes
Content-Type: text/html        # And it's in HTML format
                               # Here it comes:
<html>
  ...
</html>

The user sends a message to the chat channel:

Hello world

What happens?

Browser asks server:

POST /send HTTP/1.1                              # I'm posting data to /send
Host: chatapp.com                                # I'm posting it to chatapp.com
Content-Type: application/x-www-form-urlencoded  # I'm posting key/value pairs
                                                 # Here it comes:
message=Hello+world

Server calls the program app.rb with the posted data.

The program stores the message and prints a HTTP response.

The server takes the program output and sends it the browser:

HTTP/1.1 303 See Other   # This conversation continues elsewhere
Location: /              # Request / to continue

The browser automatically makes another request to / and gets a fresh copy of the message list.

Improving the
chat interface

  • When another user sends us a message,
    we don't notice until we reload the page
  • The message list should automatically scroll to the most recent message
  • It would be nice to be able to search through our messages

These things require code that runs in the browser, not on the server.

JavaScript

// This is a comment

let x = 1
let y = x + 1

console.log(y) // 2

Try it in your browser console! (CTRL+Shift+J)

function square(n) {
  return n * n
}

let x = square(16)
console.log(x)    // 256

let z = square
console.log(z)    // function { ... }
console.log(z(4)) // 16

HTML

one

two

three

Browser API

let div = document.querySelector('div')
let children = div.children
console.log(children) // [

one

,

two

,

three

] let parent = children[0].parentNode console.log(parent) // [
...
] console.log(parent === div) // true children[1].style.color = 'red'

View example

How to link HTML and JavaScript (1)

<html>
  <body>

    

Hi world

<script> var x = 1; var y = x + y; </script> </body> </html>

How to link HTML and JavaScript (2)

<html>
  <body>

    

Hi world

<script src="scripts.js"></script> </body> </html>

Events

<button>Click me</button>

<script>
  let button = document.querySelector('button')
  button.addEventListener('click', function(event) {
    alert('Button was clicked!')
  })

  window.addEventListener(function(event) {
    alert('User scrolled!')
  })
</script>

Client-side JavaScript that enhances server-rendered HTML is often called Progressive Enhancement or JavaScript Sprinkles.

JavaScript has sharp edges

  • Not everything is an object.
  • There are three kinds of null.
  • Equality is confusing. Also you can't define equality for your own types.
  • Objects don't crash when accessing missing properties.
  • Explicit this.foo() to call your own methods.
  • Arcane rules around this.
  • Weak standard library (strings, numbers, arrays...).
  • People are still arguing how one JavaScript file can load another.

(But it's the only language we get in the browser, so deal with it.)

Progressive enhancement
for the chat (1)

Scrolling to the bottom

function showLastMessage() {
  let lastMessage = document.querySelector('.message:last-child')
  if (lastMessage) {
    lastMessage.scrollIntoView()
  }
}

window.addEventListener('load', showLastMessage)

Progressive enhancement
for the chat (2)

How do we notice when another user sends a message?
We don't

Classic web applications
are a slide show

⇢ User requests a website
⇠ Server sends a fresh copy

⇢ User clicks a link
⇠ Server sends a fresh copy

⇢ User submits a form
⇠ Server sends a fresh copy

⇢ User reloads the page manually
⇠ Server sends a fresh copy

⇣ Another user sends a message
× Server has no way to push the update to the user

Reloading messages with fetch()

async function refresh() {
  let response = await fetch('/')
  let newHTML = await response.text()
  let parser = new DOMParser()
  let newDocument = parser.parseFromString(newHTML, 'text/html')
  let newChannel = newDocument.querySelector('.channel')
  let oldChannel = document.querySelector('.channel')
  oldChannel.replaceWith(newChannel)
}

setInterval(refresh, 1000)

Progressive enhancement
for the chat (3)

Client-side search

function search() {
  let queryInput = document.querySelector('.search-form input')
  let query = queryInput.value.toLowerCase()
  let messageElements = document.querySelectorAll('.message')
  for (let messageElement of messageElements) {
    var text = messageElement.textContent.toLowerCase()
    if (text.includes(query)) {
      messageElement.style.display = 'block'
    } else {
      messageElement.style.display = 'none'
    }
  }
}

window.addEventListener('load', function() {
  let queryInput = document.querySelector('.search-form input')
  queryInput.addEventListener('input', search)
})


The JavaScript-enhanced chat app

To run the chat app:

cd examples/2010_chat_with_javascript
bundle install
bundle exec ruby src/app.rb

More JavaScript resources and exercises

Client-side web apps

Also known as Single-page apps or SPAs.

  • HTML is rendered on the browser.
  • Often implemented using a framework (React, Angular, Vue.js, Svelte)
  • State lives in the client. State changes update the UI automatically.
  • Server is reduced to a database with a JSON API.
  • Optimistic rendering.

View source

Chat as an SPA

To run the chat app:

cd examples/2020_chat_spa
bundle install
bundle exec ruby src/app.rb

Pick your trade-off

Server-side app Client-side app
Code complexity low high
Language your choice JavaScript
Data access sync async via API
Initial load time fast slow
Optimistic rendering duplicates logic view bound to client-side state
Offline very hard hard

There are also hybrid solutions like Unpoly, htmx or Remix.

Questions?

Get in touch

@triskweline
henning.koch@makandra.de

Slides

http://webdev101.makandra.de
https://github.com/makandra/webdev101

Improving app.rb

Our current implementation has app.rb is a single script
with a lot of responsibilities:

  • Routing
  • Storage
  • Business logic

As software engineers we always try to separate responsibilities into individual modules.

Version 1

get '/' do
  @messages = File.read('messages.txt').lines
  erb(:channel)
end

get '/send' do
  message = params['message'] + "\n"
  File.write('messages.txt', message, mode: 'a')
  redirect('/')
end
 
 
 
 
 
 

SQL database

db = Mydb::Client.new(host: 'localhost')

get '/' do
  @messages = db.select_values('SELECT text FROM messages')
  erb(:channel)
end

get '/send' do
  message = params['message']
  escaped = db.escape(message)
  db.execute('INSERT INTO messages VALUES (' + escaped + ')')
  redirect('/')
end
 
 
 

Controller

channel = Channel.new

get '/' do
  @messages = channel.messages
  erb(:channel)
end

get '/send' do
  message = params['message']
  channel.add_message(message)
  redirect('/')
end
 
 
 

Model

class Channel

  def initialize
    @db = Mydb::Client.new
  end

  def messages
    @db.select_values('SELECT text FROM messages')
  end

  def add_message(message)
    escaped = @db.escape(message)
    @db.execute('INSERT INTO messages VALUES (' + escaped + ')')
  end

end

Layer cake

Layer Responsibility
Database Data storage & queries
Model O/R mapping, business logic
Controller Layer orchestration
View Content
Stylesheets Optics (optional)
JavaScript Behavior enhancements (optional)
telnet example.com 80

Browser compatibility

Your new friend: caniuse.com

Scaling the chat app