If you ask Claude Code to write a simple hello world in different languages, the result is fundamentally different:
create a hello world web app in python
Write(app.py)
⎿ Wrote 13 lines to app.py
1 from flask import Flask
2 app = Flask(__name__)
3 @app.route("/")
4 def hello():
5 return "Hello, World!"
… +3 lines (ctrl+o to expand)
create a hello world web app in go
Write(main.go)
⎿ Wrote 17 lines to main.go
1 package main
2
3 import (
4 "fmt"
5 "log"
6 "net/http"
7 )
8
9 func main() {
10 http.HandleFunc("/", func(w http.ResponseWriter, r
*http.Request) {
11 fmt.Fprintf(w, "Hello, World!")
12 })
13
14 log.Println("Server starting on :8080")
15 log.Fatal(http.ListenAndServe(":8080", nil))
16 }
In Python, it’s appropriate to immediately reach for a framework. Flask is a great one. Look how succinct and elegant the syntax is.
Meanwhile, Go forces you to do something very different. What is this function signature of http.ResponseWriter and *http.Request? Why is it so Easy in Python and so Primitive in Go?
There actually is less difference than you might think. Under the hood, Flask (through yet another layer, Werkzeug) implements a function like this (from PEP 3333):
HELLO_WORLD = b"Hello world!\n"
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]
Remarkably similar. environ holds everything an application needs to know about the HTTP request. start_response is how you interact with the web request out of band, and the return value is an iterable of bytes to act as the response content.
Go HTTP lives and breathes by the http.Handler type:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
A Request that holds everything an application needs to know about the HTTP request, a ResponseWriter to deliver the response.
So neither of these is that hard to use directly. Yet, in Python, nobody would.
We’ve just been talking about Hello World. What happens when your application wants to do more sophisticated, yet common, web application functionality like setting some sort of session cookie? This means setting an HTTP response header. You likely want this done on every request, so it’s not something you’d put directly in the Hello World handler.
This is where “Middleware” comes into play. Middleware is code that runs before (and after) the request handler.
As a simplification, we’ll say our “session cookie” is simply a header
Without a framework, this is much trickier:
def add_header(app, name, value):
def wrapped_app(environ, start_response):
def wrapped_start_response(status, headers, exc_info=None):
headers = list(headers)
headers.append((name, value))
return start_response(status, headers, exc_info)
return app(environ, wrapped_start_response)
return wrapped_app
simple_app = add_header(simple_app, "X-My-Header", "hello")
Because start_response should be called once, by the final app, it has to be wrapped. Now we have this function that returns a function that returns a function situation. It makes the mind swirl.
Thankfully, Flask has functionality for this:
from flask import Flask
app = Flask(__name__)
@app.after_request
def add_header(response):
response.headers["X-My-Header"] = "hello"
return response
Contrast this to a very common pattern in Go:
// middleware: takes a handler, returns a handler
func withHeader(next http.Handler, name, value string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(name, value)
next.ServeHTTP(w, r)
})
}
http.Handle("/", withHeader(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})))
This may not be pretty. However, the code matches the simple mental model. It’s straightforward to wrap a http.Handler interface in another http.Handler. In Python, you not only have to wrap the function, but you have to wrap start_response.
It’s these sort of details that drive the cultural difference. In Python, there are enough warts built up over decades that it’s worth it to wrap it all up in a framework. In Go, the standard library establishing the correct primitives makes it simpler to avoid adding more abstraction.
What was the point of all this? In a follow-up, I’ll talk about the design decisions in one of my own projects that I want to reference back to this.