September 29th, 2024

Some Go web dev notes

Julia Evans discusses her experiences with Go, highlighting improvements in routing, the sqlc tool for database interactions, SQLite optimization tips, and the benefits of Go's simplicity and memory management features.

Read original articleLink Icon
FrustrationAppreciationDisappointment
Some Go web dev notes

Julia Evans shares her experiences and insights gained while developing a website using Go. She highlights improvements in Go 1.22, particularly in routing, which now allows for more streamlined code compared to manual routing. She encountered issues with the built-in router, specifically with redirects involving trailing slashes, which she resolved by adopting a more conventional API design. Evans also discovered sqlc, a tool that generates Go code from SQL queries, which simplifies database interactions without the need for an ORM. She provides tips for optimizing SQLite, such as using separate database objects for reading and writing to avoid SQLITE_BUSY errors. Additionally, she discusses the introduction of a garbage collection memory limit in Go 1.19, which helped mitigate out-of-memory issues in her applications. Evans appreciates Go for its simplicity, ease of deployment, and the ability to quickly resume work on projects after long breaks. She contrasts her experiences with Go to her challenges in using Rails, emphasizing the clarity and accessibility of Go's structure. Overall, she expresses enthusiasm for the new features in Go and encourages developers to stay updated with release notes.

- Go 1.22 improves routing capabilities, allowing for cleaner code.

- sqlc generates Go code from SQL queries, reducing boilerplate.

- Optimizing SQLite involves using dedicated database objects for reading and writing.

- Go 1.19 introduced a memory limit for garbage collection to prevent OOM issues.

- Evans prefers Go for its simplicity and ease of resuming projects after breaks.

AI: What people are saying
The comments reflect a mix of experiences and opinions regarding Go, highlighting both its strengths and weaknesses.
  • Many users appreciate Go's simplicity, memory management, and the ease of returning to projects after long breaks.
  • Some users express frustration with Go's handling of null values and verbosity in error checking, comparing it unfavorably to other languages.
  • There are discussions about the limitations of the sqlc tool for database interactions, with some users preferring manual approaches.
  • Several comments mention the convenience of bundling static resources in Go applications and the importance of proper database transaction handling.
  • Users share tips and resources for optimizing SQLite usage and improving concurrency in Go applications.
Link Icon 16 comments
By @voigt - 7 months
> In general everything about it feels like it makes projects easy to work on for 5 days, abandon for 2 years, and then get back into writing code without a lot of problems.

To me this is one of the most underrated qualities of go code.

Go is a language that I started learning years ago, but did't change dramatically. So my knowledge is still useful, even almost ten years later.

By @yegle - 7 months
It's sad https://pkg.go.dev/embed was not mentioned in a post about web development in Go :-)

Having a true single binary bundling your static resources is so convenient.

By @imiric - 7 months
There are some good tips here.

As for sqlc, I really wanted to like it, but it had some major limitations and minor annoyances last time I tried it a few months ago. You might want to go through its list of issues[1] before adopting it.

Things like no support for dynamic queries[2], one-to-many relationships[3], embedded CTEs[4], composite types[5], etc.

It might work fine if you only have simple needs, but if you ever want to do something slightly sophisticated, you'll have to fallback to the manual approach. It's partly understandable, though. It cannot realistically support every feature of every DBMS, and it's explicitly not an ORM. But I still decided to stick to the manual approach for everything, instead of wondering whether something is or isn't supported by sqlc.

One tip/gotcha I recently ran into: if you run Go within containers, you should set GOMAXPROCS appropriately to avoid CPU throttling. Good explanation here[6], and solution here[7].

[1]: https://github.com/sqlc-dev/sqlc/issues/

[2]: https://github.com/sqlc-dev/sqlc/issues/3414

[3]: https://github.com/sqlc-dev/sqlc/issues/3394

[4]: https://github.com/sqlc-dev/sqlc/issues/3128

[5]: https://github.com/sqlc-dev/sqlc/issues/2760

[6]: https://kanishk.io/posts/cpu-throttling-in-containerized-go-...

[7]: https://github.com/uber-go/automaxprocs

By @gwd - 7 months
> I learned the hard way that if I don’t do this then I’ll get SQLITE_BUSY errors from two threads trying to write to the db at the same time.

OK, here's a potentially controversial opinion from someone coming into the web + DB field from writing operating systems:

1. Database transactions are designed to fail

Therefore

2. All database transactions should done in a transaction loop

Basically something like this:

https://gitlab.com/martyros/sqlutil/-/blob/master/txutil/txu...

That loop function should really have a Context so it can be cancelled; that's future work. But the idea stands -- it should be considered normal for transactions to fail, so you should always have a retry loop around them.

By @physicles - 7 months
GOMEMLIMIT has really cut down on the amount of time I’ve had to spend worrying about the GC. I’d recommend it. Plus, if you’re using kubernetes or docker, you can automatically set it to the orchestrator-managed memory limit using something like https://github.com/KimMachineGun/automemlimit — no need to add any manual config at all.
By @trustno2 - 7 months
Other note

Sooner or later you will hit html/template, and realize it's actually very weird and has a lot of weird issues.

Don't use html/template.

I grew to like Templ instead

By @srameshc - 7 months
Good to see author's mention about routing. I am mentally stuck with mux for a long time and didn't pay attention to the new release features. Happy that I always find things like these on HN.
By @codegeek - 7 months
What I love about Go is its simplicity and no framework dependency. Go is popular because it has no dominating framework. Nothing wrong with frameworks when it fits the use case but I feel that we have become over dependent on framework and Go brings that freshness about just using standard libraries to create something decent with some decent battle tested 3rd party libraries.

I personally love "library over framework" mindset and I found Go to do that best.

Also, whether you want to build a web app or cli tool, Go wins there (for me at least). And I work a lot with PHP and .NET as well and love all 3 overall.

Not to mention how easy was it for someone like me who never wrote Go before to get up and running with it quickly. Oh did I mention that I personally love the explicit error handling which gets a lot of hate (Never understood why). I can do if err != nil all day.

A big Go fan.

By @geoka9 - 7 months
I recently put together a "stack" of libraries to use for a new webapp project in Go; here's what I ended up using:

- go-chi for routing - pgx for Postgres driver - pressly/goose for migrations (I like how it can embed migrations into the binary as long as they are in SQL/not Go) - go-jet (for type-safe SQL; sort of - it lets you write 100% Go that looks like 98% SQL) - templ - htmx (not Go-specific, but feels like a match made in heaven for templ) - authboss for auth

I'm very happy with all of these choices, except maybe authboss - it's a powerful auth framework, but it took a bit of figuring out since the documentation is not very comprehensive; but it worked out in the end.

By @ncruces - 7 months
If you decide to use SQLite with one (single thread) writer pool and an other reader pool, this may help: https://github.com/bxcodec/dbresolver

Also sometimes if I have two tables where I know I’ll never need to do a JOIN between them, I’ll just put them in separate databases so that I can connect to them independently.

If this data belongs together and you're just doing this to improve concurrency, this may be a case where BEGIN CONCURRENT helps: https://sqlite.org/src/doc/begin-concurrent/doc/begin_concur...

If you want to experiment with BEGIN CONCURRENT in Go you could do worse than try my SQLite driver: https://github.com/ncruces/go-sqlite3

Import this package to get the version with BEGIN CONCURRENT: https://github.com/ncruces/go-sqlite3/tree/main/embed/bcw2

By @kristianp - 7 months
Does she (or anyone else here) use net/http's built in https support? Seems implied by saying the built-in web server is used in production.
By @rmac - 7 months
the whole gobin / gopath thing was annoying as a beginner: I just want to build this module and use that local module

go build ./... Goes where ?

By @zerr - 7 months
Are the any "fronty" back-end (or straight client/desktop) jobs using Go? i.e. I'd like to use Go on the job but all I see is AWS/Kubernetes/mix-of-DevOps kind of positions.
By @wg0 - 7 months
For application development - Go is underrated. And heavily. I say this coming from Python which is really great but I like the go's damn simplicity more which is reflected everywhere.

What makes me happy is that lots of critical infrastructure tooling is also in Go from datbases to web servers and cluster orchestrators.

By @zaptheimpaler - 7 months
I've been using go for a month now in a new job and hate it. It feels like they learned nothing from the past 20 years of language development.

Just one huge problem is that they REPEATED Java's million/billion dollar mistake with nulls. The usual way to get HTTP Headers using Go cannot distinguish between an empty header value and no header at all because the method returns "nil" for both these cases. They could've adopted option types but instead we are back to this 90s bullshit of conflating error types with valid values. If you're programming defensively, every single object reference anywhere has to be checked for nil or risk panicking now.. like why, after we literally named this a billion dollar mistake in Java, why would anyone fucking do this again?

We have helper methods in our codebase just do to this:

    fn checkThingIsA(ctx) {
      thing := ctx.get(thing)
      if thing == nil || thing != Thing.A {
        return false
      }
      return true
    }
In any sane language this is one line:

  ctx.get(thing).map(|x| x == Thing.A).unwrap_or(false)
In Go, we have to make helper methods for the simplest things because the simplest 1-liner becomes 4 lines with the nil/error check after. We have 100 helpers that do some variation of that because everything is so verbose that could would become unreadable without it.

I hate that they made and popularized this backwards dumpster fire of a language when we should know much better by now.