DEV Community

Cover image for 🤓 My top 3 Go packages that I wish I'd known about earlier
Vic Shóstak
Vic Shóstak

Posted on

🤓 My top 3 Go packages that I wish I'd known about earlier

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

GitHub logo a-h / templ

A language for writing HTML user interfaces in Go.

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:

  1. The world of the frontend, which consists almost entirely of JSX-like web frameworks, such as React.js;
  2. 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>
}

// ...
Enter fullscreen mode Exit fullscreen mode

Next, you just need to start the process of generating all *.templ files:

go run github.com/a-h/templ/cmd/templ@latest generate
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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.

↑ Table of contents

Otter

GitHub logo maypok86 / otter

A high performance lockless cache for Go. Many times faster than Ristretto and friends.

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
}
Enter fullscreen mode Exit fullscreen mode

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...
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

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:

otter benchmark

👋 You can find all benchmarks here.

↑ Table of contents

River

GitHub logo 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
}

// ...
Enter fullscreen mode Exit fullscreen mode

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,
    })
}
Enter fullscreen mode Exit fullscreen mode

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())
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

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!

↑ Table of contents

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! ❤️

↑ Table of contents

Photos and videos by

P.S.

If you want more articles (like this) on this blog, then post a comment below and subscribe to me. Thanks! 😻

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 to easily build amazing web applications with Go on the backend, using htmx & hyperscript 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.

Other my small projects: yatr, gosl, json2csv, csv2api.

Top comments (0)