Introduction.

According to a 2021 survey conducted by StackOverflow, Golang is number fourteen in terms of the most used language / tool amongst developers, right above Kotlin and below PowerShell. This is for good reasons. Its no-frills syntax, static typing, out-of-the-box build tools, and performant concurrency are a few reasons why developers, including me, prefer it over the rest. In this series of tutorials, we’ll cover how to build APIs using Golang.

Project Description.

We’ll be creating a simple project that revolves around the concept of libraries. The APIs will allow us to get a catalog of books, filter books according to multiple categories, get detailed information regarding each book, as well as borrow and return them. Seems straightforward, right?

Initialize Dependencies.

We’ll use Go Modules as our dependency management system and a couple of dependencies to setup our project:

  • Echo : performant and minimalist web framework.
  • Viper : environment variable configurations library.
  • Logrus : structured and pluggable logging library.
$ git init
$ go mod init github.com/your_github_username/create-apis-using-golang
$ go get github.com/labstack/echo/v4
$ go get github.com/spf13/viper
$ go get github.com/sirupsen/logrus

Plan Codebase Layout.

Codebase layout might be insignificant at first glance, but trust me when I say this, it will hinder your productivity in the long run if you don’t carefully plan it in the beginning. That’s why I suggest following the highly rated ones instead of just creating your own from scratch, especially if you don’t have a plan of how to structure your layout. In this series, we’ll be using the Go Standard Layout, with some modifications of course. The Github page provides thorough explanations and examples behind the reasoning of the layout and based on the repo’s 36k+ stars, I think it’s a great place to start.

Create Sample Web Server.

To run a sample web server, create a main.go inside a /internal/cmd/server directory. This dir is used to house our project’s main application and nothing else. Our main application should only comprise mostly imports from our modules.



    package main

    import (
        "log"
        "net/http"
        "time"

        "github.com/labstack/echo/v4"
    )

    func main() {
        e := echo.New()
        e.GET("/", func(c echo.Context) error {
            return c.String(http.StatusOK, "Hello, World!")
        })

        s := &http.Server{
            Addr:         ":8080",
            ReadTimeout:  2 * time.Minute,
            WriteTimeout: 2 * time.Minute,
        }

        log.Fatal(e.StartServer(s))
    }

Configure Logging Parameters.

To catch any errors our project might have, we need a way to effectively log our server. Thankfully, Logrus’s got our back. All we need to do is set our logging parameters in our main.go and the configurations will be used every time we log anything in our console.



    package main

    import (
        "net/http"
        "os"
        "time"

        "github.com/labstack/echo/v4"
        "github.com/sirupsen/logrus"
    )

    // initialize logger configurations
    func initLogger() {
        logrus.SetFormatter(&logrus.TextFormatter{
            ForceColors:     true,
            DisableSorting:  true,
            DisableColors:   false,
            FullTimestamp:   true,
            TimestampFormat: "15:04:05 02-01-2006",
        })

        logrus.SetOutput(os.Stdout)
        logrus.SetReportCaller(true)
        logrus.SetLevel(logrus.ErrorLevel)
    }

    // run initLogger() before running main()
    func init() {
        initLogger()
    }

    func main() {
        e := echo.New()
        e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
        })

        s := &http.Server{
            Addr:         ":8080",
            ReadTimeout:  2 * time.Minute,
            WriteTimeout: 2 * time.Minute,
        }

        logrus.Fatal(e.StartServer(s))
    }

Use Environment Variables.

Currently, the server is running on port 8080 and it’s hard-coded into the server instance. Since the port number used might change depending on the server’s used ports, we need to set that value as an environment variable. Environment variables are used to store interchangeable values, account credentials, and other secrets that we don’t want everyone to know.

What we need to do is create a config.yml.example in the root dir that will be used as a template for environment variables in case someone else clones our repo. Also, create a .gitignore to ignore config.yml files from being committed to our repo.



    env:
    server_port:



    config.yml

Copy-paste the config.yml.example file, rename it to config.yml, and set DEV and 8080 for env and server_port respectively. Revisiting back to the golang standard layout repo, we see that /config dir is stated to store configuration file templates or default configs. So we’ll use /internal/config dir to place our getter functions in a file called config.go and call the functions in main.go.



    package config

    import (
        "strings"

        "github.com/sirupsen/logrus"
        "github.com/spf13/viper"
    )

    func GetConf() {
        viper.AddConfigPath(".")
        viper.AddConfigPath("./..")
        viper.AddConfigPath("./../..")
        viper.SetConfigName("config")
        viper.SetEnvPrefix("svc")

        replacer := strings.NewReplacer(".", "_")
        viper.SetEnvKeyReplacer(replacer)

        viper.AutomaticEnv()
        if err := viper.ReadInConfig(); err != nil {
            logrus.Warningf("%v", err)
        }
    }

    func Env() string {
        return viper.GetString("env")
    }

    func ServerPort() string {
        return viper.GetString("server_port")
    }



    package main

    import (
        "net/http"
        "os"
        "time"

        "github.com/labstack/echo/v4"
        "github.com/sirupsen/logrus"
        "github.com/ssentinull/create-apis-using-golang/internal/config"
    )

    // initialize logger configurations
    func initLogger() {
        logLevel := logrus.ErrorLevel
        switch config.Env() {
        case "dev", "development":
            logLevel = logrus.InfoLevel
        }

        logrus.SetFormatter(&logrus.TextFormatter{
            ForceColors:     true,
            DisableSorting:  true,
            DisableColors:   false,
            FullTimestamp:   true,
            TimestampFormat: "15:04:05 02-01-2006",
        })

        logrus.SetOutput(os.Stdout)
        logrus.SetReportCaller(true)
        logrus.SetLevel(logLevel)
    }

    // run initLogger() before running main()
    func init() {
        initLogger()
    }

    func main() {
        e := echo.New()
        e.GET("/", func(c echo.Context) error {
            return c.String(http.StatusOK, "Hello, World!")
        })

        s := &http.Server{
            Addr:         ":" + config.ServerPort(),
            ReadTimeout:  2 * time.Minute,
            WriteTimeout: 2 * time.Minute,
        }

        logrus.Fatal(e.StartServer(s))
    }

Setup Daemons. 😈

To make our life easier, we need to set up a daemon that listens to changes made in the workspace so that the server can automatically restart itself on saved changes. We do this by installing Modd and setting it to listen to files with .go extensions. Create a .modd dir in our root directory and within it a server.modd.conf file. Also, create a Makefile in the root dir to abbreviate our CLI command.



    **/\*.go !**/\*\_test.go {
        daemon +sigterm: go run internal/cmd/server/main.go
    }



    # command to run the server in daemon mode
    run-server:
        @modd -f ./.modd/server.modd.conf

# run the server in daemon mode
$ make run-server

Our workspace is now complete!! 🚀 The next step would be the meat and potatoes of this series, which is building the logic of the APIs.

I hope this could be beneficial to you. Thank you for taking the time to read this article. 🙏

– ssentinull