Introduction
Hello, my DEV friends! 🙌
Today, I'd like to travel back in time, find myself at the very beginning of planning my current project, and advise myself to familiarize myself with these wonderful Go packages…
Unfortunately, I can't do that! But I can introduce you to them. Here we go 👇
📝 Table of contents
Templ
A language for writing HTML UI in Go
✨ In recent months, I have been developing web projects using GOTTHA stack: Go + Templ + Tailwind CSS + htmx + Alpine.js. As soon as I'm ready to talk about all the subtleties and pitfalls, I'll post it on my social networks.
By the way, you can subscribe! All links are here 👉 https://shostak.dev
Perhaps my favorite Go package in my entire practice of working with this programming language! The authors managed to combine two huge worlds at once:
- The world of the frontend, which consists almost entirely of JSX-like web frameworks, such as React.js;
- The world of the backend, where we are free to use our familiar Go principles and packages as usual.
Together with Templ we can write the following code:
// index.templ
package main
templ hello(name string) {
if name == "Виктор" {
<div>Привет, Виктор! Как твои дела сегодня?</div>
} else {
<div>Hello, { name }</div>
}
}
templ greeting(person Person) {
<div class="greeting">
@hello(person.Name)
</div>
}
// ...
Next, you just need to start the process of generating all *.templ
files:
go run github.com/a-h/templ/cmd/templ@latest generate
Which will be converted into a regular Go function and ready for use on your backend. For example, like this:
// main.go
package main
// ...
// Simple user struct.
type Person struct {
Name string
}
func main() {
// Create a new user with name.
user := Person{Name: "John"}
// Create Templ handler.
handler := templ.Handler(greeting(user))
// Serve Templ handler with HTTP server.
http.Handle("/", handler)
// Start simple HTTP server.
http.ListenAndServe(":3000", nil)
}
Open browser on http://localhost:3000
and see the result.
Yes, it's that simple! 😉
🔥 Templ authors have taken care of supporting popular IDE and special mode for hot-reload your
*.templ
files.
If you have never tried Templ in your code practice, then I strongly recommend doing it right now! It's really worth it.
Otter
High performance in-memory cache
I believe that the most important features of Otter are extremely simple API and autoconfiguration. And it works perfectly!
Just set the parameters you want in the builder and enjoy. Otter is automatically configured based on the parallelism of your application.
Just write like this to create a new cache instance:
// cache/new.go
package cache
// ...
// Cache struct contains all caches.
type Cache struct {
People otter.CacheWithVariableTTL[string, []Person]
}
func New() (*Cache, error) {
// Create a new cache instance for people.
peopleCache, err := otter.MustBuilder[string, []Person](100_000).WithVariableTTL().Build()
if err != nil {
return nil, err
}
return &Cache{
People: peopleCache,
}, nil
}
And now, you can use it on your app, like this:
// main.go
// ...
func main() {
// ...
// Get people from database.
people, err := dbexecutor.SelectPeople(context.Background(), "postgres://...", "SELECT * FROM people")
if err != nil {
slog.Error("can't select people from DB", "details", err.Error())
return err
}
// Create a new cache instance.
c, _ := cache.New() // don't forget to check errors!
// Save people to the cache with TTL=180m.
if c.People.Set("people", people, 180 * time.Minute) {
slog.Info("people are successfully saved to the in-memory cache")
}
// ...
// Get people from the cache.
cachedPeople, isCached := c.People.Get("people")
// Check, if data is cached.
if isCached {
// Your logic with cached data in cachedPeople here...
}
// ...
}
I would also like to point out such advantages as:
- Generics: yes, you can safely use any comparable types as keys and any types as values;
- TTL: expired values will be automatically deleted from the cache;
- Cost-based eviction: supports eviction based on the cost of each item;
- Excellent throughput: currently, Otter is the fastest cache library, with a huge lead over the competition;
- Great hit ratio: new S3-FIFO algorithm is used, which shows excellent results.
And benchmark, of course! 😊
In this benchmark 8 threads concurrently read from a cache configured with a maximum size:
👋 You can find all benchmarks here.
River
riverqueue / river
Fast and reliable background jobs in Go
Fast and reliable background jobs in Go
I'm a big fan of all the new stuff that the Go programmer community is developing. River is one of them.
It represents a combination of modern Go practices and ease of use, even if you have never worked with similar tools before.
🔥 Atomic, transaction-safe, robust job queueing for Go applications. Backed by PostgreSQL and built to scale.
And yet, this package has one of the best documentation on the market. Just read how the authors describe writing of reliable workers' principle! Love it 😘
Start with add a simple job that sorts a slice of strings:
// jobs/sort.go
package jobs
// ...
// sortArgs is a struct that contains a slice of strings to sort.
type sortArgs struct {
// Strings is a slice of strings to sort.
Strings []string `json:"strings"`
}
// Kind returns the kind of the job.
func (sortArgs) Kind() string { return "sort" }
// SortWorker is a worker that sorts a slice of strings.
type SortWorker struct {
river.WorkerDefaults[sortArgs]
}
// Work sorts a slice of strings.
func (w *SortWorker) Work(_ context.Context, job *river.Job[sortArgs]) error {
slices.Sort(job.Args.Strings)
fmt.Printf("Sorted strings: %+v\n", job.Args.Strings)
return nil
}
// ...
Next, let's create a new River queue with workers pool:
// queue/new.go
package queue
// ...
func pool() (*river.Workers, error) {
// Create a new River instance.
w := river.NewWorkers()
// Add workers to River.
_ = river.AddWorkerSafely(w, &jobs.SortWorker{}) // don't forget to check errors!
return w, nil
}
func New() (*river.Client[pgx.Tx], error) {
// Create workers pool.
workers, _ := pool() // don't forget to check errors!
// Create and return a new River client
// (for PostgreSQL over pgx/v5, by the way).
return river.NewClient(riverpgxv5.New(db), &river.Config{
Queues: map[string]river.QueueConfig{
river.QueueDefault: {MaxWorkers: 100},
},
Workers: workers,
})
}
And now, you can use it on your app, like this:
// main.go
// ...
func main() {
// ...
// Create a sample data to sort.
str := []string{"foo", "baz", "bar"}
// Create a new River instance.
q, _ := queue.New() // don't forget to check errors!
// Set job to queue.
_, err := q.Insert(context.Background(), jobs.SortArgs{Strings: str}, nil)
if err != nil {
slog.Error("can't insert job to River queue", "details", err.Error())
}
// ...
}
That's all you have to do! 💥 You can create arbitrarily complex background jobs that River can easily complete for you.
💡 River is still under rapid development, but I recommend trying it now!
And what do you use?
Tell us about it in the comments! It will be very interesting to find new Go packages and tools for yourself.
Peace to all and a successful code! ❤️
Photos and videos by
- Vic Shóstak https://github.com/koddr
P.S.
If you want more articles (like this) on this blog, then post a comment below and subscribe to me. Thanks! 😻
❗️ You can support me on Boosty, both on a permanent and on a one-time basis. All proceeds from this way will go to support my OSS projects and will energize me to create new products and articles for the community.
And of course, you can help me make developers' lives even better! Just connect to one of my projects as a contributor. It's easy!
My main projects that need your help (and stars) 👇
- 🔥 gowebly: A next-generation CLI tool that makes it easy to create amazing web applications with Go on the backend, using htmx, hyperscript or Alpine.js and the most popular CSS frameworks on the frontend.
- ✨ create-go-app: Create a new production-ready project with Go backend, frontend and deploy automation by running one CLI command.
Top comments (0)