I believe DB interaction is the principal value in web frameworks, the primary productivity boost. Easily turning ephemeral in-memory data into queryable ACID records unlocks a ton of important features for web apps.
However, most of the other features of these frameworks are just bloat, added complexity that doesn't really make things easier than writing vanilla $lang with rock-solid libraries.
For example, in Go: URL routing, request handling (middleware), html templating, websocket communication are all trivial to implement with standard or well-known libraries. But it's the DB interaction where things get tricky and very code-heavy.
I'm trying out an approach, leveraging pgx and the new Go generics, where I define a simple generic Wrapper struct with some low-hanging fruit methods: CRUD, filtered+pagified lists, unique constraints, etc.
How? `encoding/json` and Postgres JSONB columns. The generic Wrapper is hardcoded with a simple and flexible table schema: an id column and a JSONB column (I also added some timestamp metadata columns as an experiment).
The end result is that I implemented a dumb "ORM" with around 200 lines of idiomatic Go, and can do the following:
wrapper := Wrapper[MyStruct]{pgx.Conn, "mytable"}
record := wrapper.Insert(MyStruct{some,real,data}) // returns a generic Record object which includes the DB id and pointer to the (actual, not interface{}!) struct, as well as other metadata perhaps
record.Data.myField = time.Now()
wrapper.Update(record)
for i, record := range wrapper.List(Params{}) {
\\ do stuff with all the MyStructs
}
...and so on and so forth. The goal here is not to implement a perfect ORM (e.g. pointer/FK spaghetti) but to secure that initial productivity boost of easily ACIDifying my data. As my data model gets more mature, I can individually migrate these "weak" JSONB schemas to proper, (de)normalized, relational Postgres schemas and define their data access appropriately.