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 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 2026 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 streams 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, USB devices...
  • Weirdly, native apps are increasingly built using web technology
    (Electron, Tauri)

Today

  • Delivering content with HTML & HTTP
  • Styling with CSS
  • Short break
  • Server-side web apps
  • Interactivity with JavaScript
  • Single-page apps (maybe)
  • Discussion / end


Slides and code examples (pace will be fast!)

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
rgb(255 0 0 / 100%)
rgb(255 0 0 / 50%)
rgb(255 0 0 / 25%)
hsl(0 100% 50%)
hsl(30 100% 50%)
hsl(30 50% 50%)
hsl(30 50% 70%)
oklch(62.8% 0.2577 29.23)

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

(Time check 25%)

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 <i>capybara</i> 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 <i>capybara</i> 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 <i>capybara</i> 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 <i>capybara</i> 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 <i>capybara</i> 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 <i>capybara</i> 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 <i>capybara</i> 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 <i>capybara</i> belongs to the subfamily
  <b>Hydrochoerinae</b> along with ...
</p>

p:has(i) { ... }


<h1>About Capybaras</h1>

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

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

Sizes in CSS

Absolute pixels

.card {
  width: 100px;
}

Relative to font size

.card {
  width: 20em;
}

Percentage of container

.card {
  width: 50%;
}

Expression

.card {
  width: calc(50% + 15px);
}

Shorthand properties

.card {
  padding-top: 10px;
  padding-right: 20px;
  padding-bottom: 10px;
  padding-left: 20px;
}
.card {
  padding: 10px 20px;
}

Custom properties (variables)

a {
  color: #ff2200;
}

.card {
  border-style: solid;
  border-width: 2px;
  border-color: #ff2200;
}
html {
  --accent: #ff2200;
}

a {
  color: var(--accent);
}

.card {
  border-style: solid;
  border-width: 2px;
  border-color: var(--accent);
}

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%;
}
  

flex-direction


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

flex-direction


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

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;
}
  

Flexbox resources

Advanced users can take a look at CSS grids.

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, buttons, modals, tabs, etc.)
  • Utility classes

View example

Utility-first CSS frameworks

Frameworks like Tailwind do not extract components like cards or buttons. They only offer utility classes:


De-duplicate with a component system that abstracts away your HTML.

Wouldn't recommend when you're just learning CSS.

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 at the bottom

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('scroll', 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

  • Designed in 10 days in 1995, 30 years of design-by-committee.
  • Not everything is an object.
  • There are three kinds of null.
  • Equality of basic values is confusing.
  • No built-in way to compare structured objects or arrays.
  • Objects are hash maps. Calling undefined properties does not crash and returns undefined.
  • Explicit this.foo() required 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.

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)

Advanced users can take a look at WebSockets or Server-Sent Events.

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.

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 React Server Components.

Questions?

Get in touch

@triskweline
henning.koch@makandra.de

Slides

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

The box model

View example

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