What Happens When You Make the Same Software on GO + Htmx vs JS (Express JS)?

The Origin of the Discussion
A recent conversation with a friend sparked this deep dive into programming languages. Our debate centered on programming languages and their impact on the development cycle. While technical discussions often revolve around performance, low-level versus high-level characteristics, we realized we hadn't truly understood the nuanced experience of programming in different languages within production-class systems.
Experimental Framework
Goal
Evaluate the developer experience of different frameworks using a comprehensive, all-rounded project.
Project Selection: Video Conferencing Application
Rationale for Chosen Project
Elaborate full-stack project involving complex technologies
Requires handling:
HTTP protocols
WebSockets
WebRTC
Concurrency management
Demonstrates pros and cons of different frameworks
Balances feature complexity with performance requirements
Library Selection Criteria
Libraries were carefully chosen based on:
Most comprehensive tutorials
Most starred/used GitHub repositories
Consultation with Large Language Models (LLMs)
Phased Project Approach
The project was methodically structured into five progressive stages:
Chatroom with WebSockets
Video Conferencing Implementation
Users and Multiple Rooms Support
Authentication and Database Integration
Comprehensive Testing
Hypothesis: This approach covers most commonly used web development utilities and provides a robust comparison ground.
Deep Dive: WebSocket Chatroom Implementation
Architectural Considerations
Golang Implementation
Hub Structure
type Hub struct {
sync.RWMutex
clients map[*Client]bool
broadcast chan *Message
register chan *Client
unregister chan *Client
messages []*Message
}
Channel and Concurrency Characteristics
Channels manage events through unique behavioral patterns
sync.RWMutex provides thread-safe operations
Explicit handling of client connections and message propagation
Data Flow Mechanism
Main Server (Hub) Initialization:
register channel: Client connection registration
unregister channel: Client disconnection management
broadcast channel: Message distribution to all clients
Client Goroutine Operations:
Read Goroutine:
Captures WebSocket messages
Decodes incoming messages
Forwards decoded messages to Hub via broadcast channel
Write Goroutine:
Monitors client's send channel
Writes messages to WebSocket connection
Sends periodic ping messages to maintain connection
Message Propagation Workflow:
- Client message → Read goroutine → Hub broadcast channel → Write goroutine → Client WebSocket
JavaScript Implementation
Socket.IO Selection Rationale
Preferred over raw WebSockets
More prevalent in production environments
Provides robust connection management
Code Architecture
Divided into two primary components:
Public-side (client browser)
Server-side code
High-Level Communication Flow
Server Setup:
Listens for incoming WebSocket connections
Handles message broadcasting
Manages client interactions
Client Setup:
Connects with authentication details
Receives welcome messages
Sends and receives messages
Detailed Client-Server Interaction
Initial Connection:
Client connects to server
Server sends welcome message
Connection Acknowledgment:
- Client responds with "hello all" message
Message Transmission:
Client sends message to server
Server broadcasts to all connected clients
Comparative Analysis and Insights
Abstraction Levels
JavaScript Approach:
High-level abstractions
Implementation details obscured
Enables focus on feature development
Quick prototyping capabilities
Golang Approach:
Mid-level language exposure
Explicit system-level control
Concurrency responsibility placed on developer
Enables development of performant systems
Development Prerequisites
JavaScript:
Lower technical entry barrier
Can create functional applications quickly
Relies heavily on library abstractions
Minimal understanding of underlying mechanisms required
Golang:
Higher technical prerequisites
Requires deeper understanding of:
WebSocket protocols
Concurrency management
System-level interactions
More explicit control over implementation
Philosophical Paradigms: Backend vs Frontend Approaches
Go + HTMX: Backend Engineer's Frontend
Go + HTMX represents a paradigm shift where backend engineers reclaim frontend development:
Server-Side Rendering (SSR):
Computation happens on the server
Minimal client-side JavaScript
HTML returned as the primary interface
Backend logic drives user interactions
Developer Experience:
Backend engineers feel more comfortable
Direct control over application flow
Performance-optimized rendering
Reduced client-side complexity
Architectural Characteristics:
Strongly typed
Explicit data flow
Predictable state management
Lightweight client-side footprint
JavaScript: Frontend's Backend
JavaScript, conversely, represents a frontend-driven backend approach:
Client-Side Dominance:
Complex logic runs in the browser
Heavy client-side computations
Dynamic, interactive experiences
Backend becomes primarily a data service
Developer Experience:
Frontend developers extend backend capabilities
Rich, interactive user interfaces
Flexible state management
Rapid prototyping
Architectural Characteristics:
Dynamic typing
Event-driven architecture
Extensive ecosystem
Complex client-side state management
The Philosophical Divide
Go + HTMX says: "The server knows best, clients are thin." JavaScript says: "The client is king, backend serves its needs."
This fundamental philosophical difference isn't about technical superiority, but about different problem-solving approaches and development perspectives.
Architectural Philosophies
Go + HTMX: Backend Engineer's Frontend
Server-side rendering primacy
Minimal JavaScript requirements
Performance-optimized architecture
Direct application flow control
JavaScript: Frontend's Backend
Rich ecosystem of libraries
Event-driven architecture
Extensive client-side capabilities
Rapid feature implementation
Personal Insights and Motivations
Why This Project Over Reading?
Theoretical understanding has limitations. This hands-on exploration revealed nuances impossible to capture through passive reading:
Practical implementation challenges
Real-world design decision impacts
Tangible differences in language philosophies
Personal Developmental Wins
Enhanced language design understanding
Improved context-switching skills
Practice with design pattern variations
Exploration of new development workflows
Hands-on experience with Neovim and Lazygit
Concluding Thoughts
Go and JavaScript represent fundamentally different problem-solving approaches. Their architectural differences enforce unique developmental paradigms.
The true power lies not in declaring a superior technology, but in understanding each language's strengths and selecting the right tool for specific project requirements.
May your code be elegant, and your debugging be swift! 🚀
Subscribe to my newsletter
Read articles from kafka franz directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
