

Hexago is a CLI tool for you to create a Go project by applying hexagonal architecture.
TOC
Installation
go install github.com/ksckaan1/hexago@latest
Dependencies
[!WARNING]
Make sure that the directory $HOME/go/bin
is appended to the $PATH
environment variable
Before We Start
If you didn’t hear about hexagonal architecture before, firstly, you could research about it.
Here it is nice blog posts about hexagonal architecture:
Why Hexago?
Hexago can be used to create hexagonal Go projects in an organised way. In this way, you can follow certain standards and have a more manageable application development phase. It imposes its own rules for certain situations and as a result of these impositions, your project gains regularity.
You can also use Hexago only for creating hexagonal projects. It is your preference whether or not to bring it with hexago.
Commands
doctor
The doctor
command displays the status of dependencies that are required for hexago to run properly.
Example:

init
The init
command initialize a Hexago project. This command creates a domain named core
by default. Promts go module name. If leaves blank, uses project folder name as lowercase defaultly.
hexago init <project-path>
Example:

domain
This is the parent command for all domain-related operations.
If the project does not contain any domain, a new service
and app
cannot be created. For this, a domain must be created first.
port
This is the parent command for all port-related operations.
Ports can be implemented when creating service, app, infrastructure and package. If there is no port in the project, it is not asked which port to implement in the creation screen.
You can create a port manually like bellow.
// internal/port/user.go
package port
type UserController interface {
CreateUser(w http.ResponseWriter, r *http.Request)
GetUserByID(w http.ResponseWriter, r *http.Request)
GetAllUsers(w http.ResponseWriter, r *http.Request)
UpdateUserByID(w http.ResponseWriter, r *http.Request)
DeleteUserByID(w http.ResponseWriter, r *http.Request)
}
type UserService interface {
CreateUser(ctx context.Context, params dto.CreateUserParams) (string, error)
GetUserByID(ctx context.Context, userID string) (*dto.User, error)
GetAllUsers(ctx context.Context) ([]*dto.User, error)
UpdateUserByID(ctx context.Context, params dto.UpdateUserParams) error
DeleteUserByID(ctx context.Context, userID string) error
}
type UserRepository interface {
CreateUser(ctx context.Context, params dto.CreateUserParams) (string, error)
GetUserByID(ctx context.Context, userID string) (*dto.User, error)
GetAllUsers(ctx context.Context) ([]*dto.User, error)
UpdateUserByID(ctx context.Context, params dto.UpdateUserParams) error
DeleteUserByID(ctx context.Context, userID string) error
}
You can use this port when creating a new service, app, infrastructure or package.
service
This is the parent command for all service-related (domain-service) operations.
-
new
This command creates a new service under the internal/domain/<domainname>/service/<servicename>
directory.
Domain is required to create a service. Steps applied when creating a service:
- Insert service name (PascalCase)
- Insert folder name (lowercase)
- Select a domain
- Select port which will be implemented (skips this step if there is no port)
- Assert port if selected
hexago service new

-
ls
This command lists all services under the internal/domain/<domainname>/service
directory.
hexago service ls
Flags:
-l
: lists services line-by-line

app
This is the parent command for all application-related (application-service) operations.
Application services are the places where endpoints such as controllers or cli applications are hosted.
-
new
This command creates a new application under the internal/domain/<domainname>/app/<appname>
directory.
Domain is required to create an application. Steps applied when creating an application:
- Insert application name (PascalCase)
- Insert folder name (lowercase)
- Select a domain
- Select port which will be implemented (skips this step if there is no port)
- Assert port if selected
hexago app new

-
ls
This command lists all applications under the internal/domain/<domainname>/app
directory.
hexago app ls
Flags:
-l
: lists applications line-by-line

infra
This is the parent command for all infrastructure-related operations.
Infrastructures host databases (repositories), cache adapters or APIs that we depend on while writing applications
-
new
This command creates a new infrastructure under the internal/infrastructure/<infraname>
directory.
Steps applied when creating an infrastructure:
- Insert infrastructure name (PascalCase)
- Insert folder name (lowercase)
- Select port which will be implemented (skips this step if there is no port)
- Assert port if selected
hexago infra new

-
ls
This command lists all infrastructures under the internal/infrastructure
directory.
hexago infra ls
Flags:
-l
: lists infrastructures line-by-line

pkg
This is the parent command for all package-related operations.
Packages are the location where we host features such as utils. There are two types of packages in a hexago project.
-
The first one is located under /internal/pkg
and is not imported by other go developers. Only you use these packages in the project.
-
The second is located under /pkg
. The packages here can be used both by your project and by other go developers.
-
new
This command creates a new package under the internal/pkg/<pkgname>
or /pkg/<pkgname>
directory.
Steps applied when creating a package:
- Insert package name (PascalCase)
- Insert folder name (lowercase)
- Select port which will be implemented (skips this step if there is no port)
- Assert port if selected
- Select package scope (global or internal)
hexago pkg new

-
ls
This command lists all packages under the internal/pkg
or /pkg
directory.
hexago pkg ls # list internal packages
Flags:
-g
: lists global packages
-a
: list both global and internal packages.
-l
: lists packages line-by-line

cmd
This is the parent command for all entry point-related (cmd) operations.
Entry points are the places where a go application will start running. entry points are located under the cmd
directory.
-
new
This command creates a new entry point under the cmd/<entry-point-name>
directory.
There is only one step creating an entry point.
- Insert entry point folder name (kebab-case)
Creates a go file like bellow.
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func main() {
fmt.Printf("Hello from %s!\n", strings.ToUpper(filepath.Base(os.Args[0])))
if env, ok := os.LookupEnv("MY_ENV"); ok {
fmt.Println("MY_ENV ->", env)
}
}
hexago cmd new

-
ls
This command lists all entry points under the cmd
directory.
hexago cmd ls
Flags:
-l
: lists entry points line-by-line

run
This command can be used for two different purposes. the run
command create a log file under the logs
directory defaultly.
-
Firstly, if there is an entry point in your project, it can be used to run this entry point.
hexago run <entry-point-name>

Flags:

You can customize this run command with given entry point in .hexago/config.yaml
file.
You can specify all envs from config.yaml
file like bellow.
templates: # std | do | <custom>
service: std
application: std
infrastructure: std
package: std
runners:
api: # it runs "go run ./cmd/api", if exists
env:
- ENV_KEY1=ENV_VAL1
- ENV_KEY2=ENV_VAL2
log:
disable: false # write logs to files
seperate_files: true # create log files seperately as api.stderr.log and api.stdout.log
overwrite: true # create new log file when runner called
When the hexago run api
command is executed as above, it starts the api
entry point according to the settings in the config.yaml
file.
-
As a second method, you can use the run
sub-command as an alternative to makefile. You can create a new entry in the runners
section of the .hexago/config.yaml
file to call it with the run
command.
The special commands created do not need to have an entry point equivalent. We can add a special command using the cmd
key.
runners:
custom-command:
cmd: "go version" # overwrite default "go run ./cmd/mycommand/" command
log:
disabled: true # do not print log file
When you run hexago run custom-command
command, you will get the following result.
go version go1.23.0 darwin/arm64
tree
This command prints hexagonal structure of project.
hexago tree

Templates
When creating service, application, infrastructure and package with Hexago, templates are used to create go files. Hexago has 2 built-in templates, std
and do
.
std
template uses the standard go instance initialiser.
package mypkg
type MyPkg struct{}
func New() (*MyPkg, error) {
return &MyPkg{}, nil
}
do
is a package that provides a dependency injection container.
package mypkg
import "github.com/samber/do"
type MyPkg struct{}
func New(i *do.Injector) (*MyPkg, error) {
return &MyPkg{}, nil
}
Which template to use can be determined in the .hexago/config.yaml
file.
templates: # std | do | <custom>
service: std
application: do
infrastructure: std
package: do
Custom Templates
If you want to use another template other than these templates, you can create your own template.
Naming Custom Template
Custom templates are hosted under the .hexago/templates/
directory. There is a convention that must be used when naming the template file.
.hexago/templates/<template-name>_<template-type>.tmpl
Examples:
.hexago/templates/abc_service.tmpl
.hexago/templates/abc_application.tmpl
.hexago/templates/abc_infrastructure.tmpl
.hexago/templates/abc_package.tmpl
Content of Custom Template
For example, the std
template is as follows.
package {{.PkgName}}
{{if and .AssertInterface (ne .InterfaceName "") (ne .ImportPath "")}}
import "{{.ImportPath}}"
var _ port.{{.InterfaceName}} = (*{{.StructName}})(nil)
{{end}}
type {{.StructName}} struct{}
func New() (*{{.StructName}}, error) {
return &{{.StructName}}{}, nil
}
{{ if ne .Implementation "" }}{{ .Implementation }}{{end}}
Selecting Custom Template to Use
To use the created custom template, you must specify the template name in config.yaml
.
templates: # std | do | <custom>
service: abc
application: std
infrastructure: std
package: std