<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Haseeb Majid</title>
    <description>The latest articles on Forem by Haseeb Majid (@hmajid2301).</description>
    <link>https://forem.com/hmajid2301</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3111195%2Fee0e3324-4ba1-4b46-8a8b-2ec1e32c3eff.jpeg</url>
      <title>Forem: Haseeb Majid</title>
      <link>https://forem.com/hmajid2301</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hmajid2301"/>
    <language>en</language>
    <item>
      <title>Why I Built a Web App With HTMX, Go &amp; Postgres</title>
      <dc:creator>Haseeb Majid</dc:creator>
      <pubDate>Fri, 02 May 2025 21:50:58 +0000</pubDate>
      <link>https://forem.com/hmajid2301/why-i-build-a-web-app-with-htmx-go-postgres-1hnd</link>
      <guid>https://forem.com/hmajid2301/why-i-build-a-web-app-with-htmx-go-postgres-1hnd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, I have started to build apps using the web stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;frontend&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;htmx&lt;/li&gt;
&lt;li&gt;tailwindcss

&lt;ul&gt;
&lt;li&gt;daisyui&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;alpine&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;backend&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;go

&lt;ul&gt;
&lt;li&gt;templ&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;postgres&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;devex&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gitlab ci&lt;/li&gt;
&lt;li&gt;nix devshells&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I have now built two apps using this stack, one of them being &lt;code&gt;banterbus.games&lt;/code&gt; a browser-based party game (currently broken).&lt;br&gt;
Using web sockets, so it isn't the most normal web application. As there are a few quirks of web sockets.&lt;/p&gt;

&lt;p&gt;Banter Bus Code: &lt;a href="https://gitlab.com/hmajid2301/banterbus" rel="noopener noreferrer"&gt;https://gitlab.com/hmajid2301/banterbus&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am currently building a more normal CRUD app, called Voxicle (&lt;a href="https://voxicle.app" rel="noopener noreferrer"&gt;https://voxicle.app&lt;/a&gt;), a SaaS platform for collecting&lt;br&gt;
and acting on user feedback. The first time, I am trying to build something which I will charge others for (hence&lt;br&gt;
the code also not being open-source yet). Some more context here: &lt;a href="https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/" rel="noopener noreferrer"&gt;https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, go over why I like this tech stack so much. Mainly because I want to keep things as simple as possible.&lt;br&gt;
One other thing to note, I am a backend developer, and don't really like writing frontend code a la JavaScript (JS).&lt;br&gt;
So this stack is designed to avoid writing as many JS as I can, which other developers, of course love writing.&lt;br&gt;
So read this article through those lenses. This stack might not make sense for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;One of the issues I found in my side projects was that I would always lose motivation when I had to write frontend&lt;br&gt;
code. For a few reasons, I never had enough experience with frameworks like React or Svelte to know how to structure&lt;br&gt;
them. Therefore, they would quickly become a mess.&lt;/p&gt;

&lt;p&gt;Another issue is maintaining consistent state between the backend and frontend. If we drive everything from the backend&lt;br&gt;
we can maintain most of our state there. Which works pretty well for a simple CRUD app we are building? The UI&lt;br&gt;
doesn't need to do anything super crazy or clever.&lt;/p&gt;
&lt;h3&gt;
  
  
  TailwindCSS
&lt;/h3&gt;

&lt;p&gt;For styling, I have stuck with TailwindCSS for a number of years, some people hate it. I have never minded it.&lt;br&gt;
It does lead to bloated class and makes the HTML look more complicated. But I never worked out how to structure&lt;br&gt;
CSS. So tailwind always felt simpler to me.&lt;/p&gt;

&lt;p&gt;I probably have a bunch of duplicated styles and can simplify what I have, as again I am no CSS expert nor&lt;br&gt;
frontend expert. But we can look at that if we feel the frontend is slow to load etc.&lt;/p&gt;
&lt;h4&gt;
  
  
  DaisyUI
&lt;/h4&gt;

&lt;p&gt;I am using this component library on top of TailwindCSS, which provides us with a bunch of components and themes&lt;br&gt;
we can use out of the box. Straightforward to style and tweak/change. I like it again, backend developer doing frontend&lt;br&gt;
so perhaps for more complicated apps, you want your own design system and creating your own components is better.&lt;br&gt;
But again, I really like how easy it makes frontend development feel for me.&lt;/p&gt;
&lt;h3&gt;
  
  
  HTMX
&lt;/h3&gt;

&lt;p&gt;The frontend is built using HTMX, as a simple library, where we drive most of our state from the backend.&lt;br&gt;
The server returns HTML (vs JSON) and then we tell it where to replace. Look at this HTMX request.&lt;br&gt;
When the form is submitted, we send a POST request to the &lt;code&gt;/feedback&lt;/code&gt; endpoint, the body is the input fields as JSON.&lt;br&gt;
(&lt;code&gt;json-enc&lt;/code&gt; extension converts it to JSON for us). Then the target means the HTML returned replaces the outer HTML&lt;br&gt;
&lt;code&gt;feedback_list&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
    &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/feedback"&lt;/span&gt;
    &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#feedback_list"&lt;/span&gt;
    &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;
    &lt;span class="na"&gt;hx-ext=&lt;/span&gt;&lt;span class="s"&gt;"json-enc"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"space-y-6"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AlpineJS
&lt;/h3&gt;

&lt;p&gt;I combined HTMX with AlpineJS, as some actions you don't always want to go to the server. Like opening a modal,&lt;br&gt;
or accordion. I don't use it a lot and maybe could refactor my site without it. But for now, it does help&lt;br&gt;
make things a bit simpler. Especially with Dynamic HTMX queries, which cannot be statically generated on the backend&lt;br&gt;
when we render the HTML template.&lt;br&gt;
See an example here: &lt;a href="https://haseebmajid.dev/posts/2025-04-29-til-set-dynamic-url-with-htmx-and-alpinejs/" rel="noopener noreferrer"&gt;https://haseebmajid.dev/posts/2025-04-29-til-set-dynamic-url-with-htmx-and-alpinejs/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;

&lt;p&gt;I moved from Python to Go about 3 years ago and honestly haven't looked back. I really liked statically typed&lt;br&gt;
languages. Again, each to their own, but I think having typed data just makes your life so much easier. I also really&lt;br&gt;
like the smaller, better devex around Go. Fast to compile. Compiles to single static binary. Easier to put into a scratch&lt;br&gt;
Docker image. Simple dependency management, &lt;code&gt;go mod&lt;/code&gt;. The CLI tool has a test runner (&lt;code&gt;go test&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I really like the simplicity of Go, the code is pretty easy to read and see what is going on. I have tried to avoid&lt;br&gt;
using frameworks like Gin, or Gorilla. In the end, I did use Fuego, so I could generate an Open API specification.&lt;/p&gt;

&lt;p&gt;I started writing raw SQL with &lt;code&gt;sqlc&lt;/code&gt;, so I know exactly what the query will do vs guessing with an ORM.&lt;br&gt;
I loved using ORMs and frameworks like Flask or FastAPI in Python but have completely reversed my opinion in Go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- name: AddUser :one&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- name: AddOrganization :one&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;organizations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can generate the SQL code by running &lt;code&gt;sqlc generate&lt;/code&gt; which generates go code. We are going to use &lt;code&gt;pgx&lt;/code&gt; as&lt;br&gt;
the underlying driver.&lt;/p&gt;

&lt;p&gt;I am using the common 3 layer structure, controller (return HTML) -&amp;gt; service layer (business logic) -&amp;gt; store layer (DB).&lt;/p&gt;

&lt;h3&gt;
  
  
  Postgres
&lt;/h3&gt;

&lt;p&gt;I remember I used to really like experimenting with different technologies like MongoDB. But these days, boring is sexy.&lt;br&gt;
For me like this entire stack, keep it simple, stupid. Unless you have a good reason, just use Postgres. It works&lt;br&gt;
there is a lot of tooling around it. For some instances, storing JSON and querying JSON, it outperforms MongoDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  DevEx
&lt;/h2&gt;

&lt;p&gt;A few points on the DevEx part, I really like Gitlab CI, I self-host my own runners. So speed/running out of minutes&lt;br&gt;
is not an issue. I am just very familiar with it. I use it alongside &lt;code&gt;go-task&lt;/code&gt; (Taskfiles), which I found simpler&lt;br&gt;
than make.&lt;/p&gt;

&lt;p&gt;Finally, if you know me, I always love to mention Nix, the project is set up to use Nix dev shells for all the non go&lt;br&gt;
dependencies, and we then have a really similar dev env and CI env. See more: &lt;a href="https://www.youtube.com/watch?v=bdGfn_ihHO" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=bdGfn_ihHO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's about it! That is my high-level experience and reason for going with this tech stack for building a SaaS.&lt;br&gt;
I may well go into more details about the platform I choose to help with authentication and how I intend to do&lt;br&gt;
authorization, but for now, this is a good starting point.&lt;/p&gt;

</description>
      <category>htmx</category>
      <category>postgres</category>
      <category>go</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
