An example of Golang project template

This project is a mix of various techniques I found helpful.

Almost all time I starts a new Golang project I spend a valuable time to write boilerplate code, adjucts folder structure and many thing I found boring.

The projecto is component based application layout.

Let's start with folder structure description.

.
├── bin # a bin folder contains compiled binary of application
│   ├── app
│   └── generator
├── config.yml # configuration file passed as flag to application
├── Makefile   # task to build\generate\test\lint etc
├── src        # a source folder
│   ├── app
│   │   └── app.go # it's a main application package 
│   ├── cmd        
│   │   └── app
│   │       └── main.go  # here is application entry
│   ├── config
│   │   └── config.go    # config component
│   ├── db
│   │   └── db.go        # database component
│   ├── generator
│   │   └── main.go      # a generator for database repo generation
│   ├── go.mod
│   ├── go.sum
│   ├── model            # models
│   │   └── item.go
│   ├── service          # service components
│   │   └── item
│   │       ├── itemrepo # item database repository
│   │       │   ├── itemrepo.go
│   │       │   └── repo.gen.go
│   │       └── service.go # item service
│   └── web              # api components with http echo         
│       ├── info.go
│       ├── items.go
│       ├── items_test.go
│       └── web.go
└── templates            # template for generate per model database repository
    └── repo.go.tmpl

15 directories, 22 files

As you can see I'm placed all source under the src directory, Makefile contains commands to generate code and build binary.

Clone project and open src/app/app.go This file contains an entity which responsible to build a components tree.

Every component should resolve Component interface.

type Component interface {
    Setup(a *App) (err error)
    Name() (name string)
}

There is also ComponentRunnable interface it's runnable component a daemon or cronjob or anything else which must runs in the background.

type ComponentRunnable interface {
    Component
    Run() error
    Close() error
}

A Register method of App struct registers component in the components tree, so any other component able to retrive it by its name.

func (app *App) Register(s Component) *App {
    app.mu.Lock()
    defer app.mu.Unlock()
    for _, es := range app.components {
        if s.Name() == es.Name() {
            panic(fmt.Errorf("component '%s' already registered", s.Name()))
        }
    }
    app.components = append(app.components, s)
    return app
}

Lets take a look for example to the database component a Setup method:

func (db *Database) Setup(a *app.App) (err error) {
    // Require config component, MustComponent will panic if config component wasn't found 
    conn := a.MustComponent("config").(config).GetDB() 
    if db.Database, err = gorm.Open(postgres.Open(conn), &gorm.Config{}); err != nil {
        return err
    }
    return
}

In order to run this application invoke make and run ./bin/app -c config. You need a database runned, below is one command with docker postgres container:

docker run --name dev-postgres -e POSTGRES_PASSWORD=Pass2020! -v $(pwd)/pg-data/:/var/lib/postgresql/data  -p 5432:5432 postgres

Create a database:

docker exec -it dev-postgres psql -U postgres
psql (12.4 (Debian 12.4-1.pgdg100+1))
Type "help" for help.

postgres=# CREATE DATABASE projecto;

Run the application:

projecto (master) ./bin/app -c config.yml

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on 127.0.0.1:8080
INFO[0000] app projecto 76293b9-master started           app=true

Try to send some curl request.

Create an item:

curl -X POST localhost:8080/v1/items -d '{ "name": "", "price": 0 }'
{"error":{"name":"cannot be blank","price":"cannot be blank"}}

Get item list:

curl localhost:8080/v1/items
[
  {
    "id": 1,
    "name": "foo",
    "price": 123
  },
  {
    "id": 2,
    "name": "bar",
    "price": 124
  }
]

There is a validation in the model package src/model/item.go. It's the ozzo-validation.

I'll end on this and suggest you to explore source yourself.

Batteries included:

  • echo http framework
  • gorm postgres database
  • code generation
  • basic github actions
  • golangci-lint
  • basic tests for http API
  • basic ldflags for passing launch arguments

Feel free to send me your thoughts. Happy coding!