5 minutes
My Go Toolset
When developing some software, you very often tend to use some 3rd party tools or libraries to enable yourself to focus more on the task at hand. And this makes perfect sense, imagine if you had to write your own HTTP server everytime you wanted to develop a web application. Or a router, or, or, or… This would be insane and nothing would get done. Ever.
We as developers also grow used to certain tools, and don’t switch much between them, unless we really have to due to many reasons. And here is the list of my tools and libraries that I tend to use, with short description of each of them bellow:
Lets dig in!
go-delve/delve - Debugging
When it comes to debugging Go applications, go-delve/delve is my go-to library. To start debugging your source code, simply execute:
dlv debug gitlab.com/youruser/project/
Or to debug a built binary:
dlv exec $GOPATH/bin/built_bin
Even debug your pesky tests:
dlv test gitlab.com/youruser/project
Now you can start setting breakpoints using break pkg_name.FuncName
or break filename.go:<linenum>
and then starting the execution by calling continue
.
You can also start the debugging session with the --headless
flag to start the
debugger in headless mode and connect to it with your editor or IDE.
spf13/cobra - CLI commands
Creating a CLI interface to my applications is really simple when using the spf13/cobra library, all you need to do is execute:
cobra init nameofexec --pkg-name gitlab.com/yourname/yourproject
And cobra will create everything, the directory structure inside a new directory named nameofexec, the root command inside nameofexec/cmd/root.go, all you need to do is open the file, edit the descriptions and tell the command what you want it to actually do.
Need a new command to go with your executable? No problem:
cobra add mycmd
This will create the nameofexec/cmd/mycmd.go file and register the command. Again, all you need to do is set descriptions and tell it what to do.
Of course you can also add flags and options to your executable and/or commands, but this is beyond the scope of this article.
spf13/viper - Configuration
Need to parse configuration files? Need to parse configuration from a distributed key-value store like etcd? Look no further, spf13/viper got you covered! If you’re using spf13/cobra from above, it already imports and configures spf13/viper for you, if not, configuration is really simple:
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".yourproject" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".yourproject")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
Accessing the configuration is even simpler:
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
fmt.Println(viper.GetString("my.key"))
}
It supports multiple file types, JSON, YAML, TOML,… Whatever you need!
sirupsen/logrus - Logging
To log my messages nicely under different levels or even to different destinations I let sirupsen/logrus handle it. It doesn’t even need any configuration, you can just use it from the get go:
package main
import "github.com/sirupsen/logrus"
func main() {
logrus.Info("Hello!")
details := "important stuff"
logrus.WithField("details", details).Info("I've got something important to tell you")
err := anything.Do()
if err != nil {
logrus.WithError(err).Fatal("I couldn't do anything!")
}
}
Of course this is just basic usage, you can set the desired log level to ignore messages that are in lower levels and log just the errors, you have countless hooks readily available to help you send your log messages to any service you want, and if the hook isn’t available, it’s really easy to write one yourself.
gorilla/mux - HTTP Router
When writing a web application or a RESTful API, you need a HTTP router and dispatcher. I tend to always use gorilla/mux in my projects. It satisfies all my needs, and is very simple to use:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
rtr := mux.NewRouter()
rtr.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, "Hello, World!")
})
http.ListenAndServe(":8080", rtr)
}
Compile, run and visit http://localhost:8080/hello
and there you go! Want to
make it more personal? No problem let’s just change the above HandleFunc
to
include a parameter or leave the above one in place and create a new route:
// ...
rtr.HandleFunc("/hello/{name}", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello, %s!", mux.Vars(req)["name"])
})
// ...
Compile, run and visit http://localhost:8080/hello/foo
.
adams-sarah/test2doc - API documentation
I must say I simply love adams-sarah/test2doc! Writing tests can be a daunting task, and then you still need to write documentation. Well here comes test2doc to the rescue! Simply write up a test for a handler using the test2doc server:
package main
import (
"testing"
"github.com/adams-sarah/test2doc/test"
"github.com/adams-sarah/test2doc/vars"
)
func TestMyHandler(t *testing.T) {
// init the router and register a handler to a route
rtr := mux.NewRouter()
rtr.HandleFunc("/hello", helloHandler)
// register the mux extractor
test.RegisterURLVarExtractor(vars.MakeGorillaMuxExtractor(rtr))
// start the server and defer the Finish call
var err error
server, err = test.NewServer(rtr)
if err != nil {
t.Errorf("unable to create test server, %s", err)
}
defer server.Finish()
// get the named route from mux and get its URL
urlPath, err := rtr.Get("hello").URL()
if err != nil {
t.Errorf("unable to get route URL, %s", err)
}
resp, err := http.Get(server.URL + urlPath.String())
if err != nil {
t.Errorf("unable to process the request, %s", err)
}
}
Now simply execute the test with go test
and you should get a new file in your
directory ending with an .apib
extension.
Web application directory layout
I have also prepared a small bare-bones project that I tend to use often when starting a new project, since I already have a lot of the above tools and libraries already incorporated, as well as some deployment options. It is personalized to my own use, but can probably be useful for someone else too, since it does not enforce anything. Please check it out at my GitLab repository and feel free to use it if you find it useful!