Hexago is a CLI tool for you to create a Go project by applying hexagonal architecture.
go install github.com/ksckaan1/hexago@latestWarning
Make sure that the directory $HOME/go/bin is appended to the $PATH environment variable
If you didn’t hear about hexagonal architecture before, firstly, you could research about it.
Here it is nice blog posts about hexagonal architecture:
- https://medium.com/ssense-tech/hexagonal-architecture-there-are-always-two-sides-to-every-story-bc0780ed7d9c
- https://dev.to/bagashiz/building-restful-api-with-hexagonal-architecture-in-go-1mij
- https://medium.com/@janishar.ali/how-to-architecture-good-go-backend-rest-api-services-14cc4730c05b
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.
The doctor command displays the status of dependencies that are required for hexago to run properly.
Example:
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:
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.
-
This command creates a new domain under the
internal/domaindirectory.hexago domain new
-
This command lists all domains under the
internal/domaindirectory.hexago domain ls
Flags:
-l: lists domains line-by-line
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.
This is the parent command for all service-related (domain-service) operations.
-
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
-
This command lists all services under the
internal/domain/<domainname>/servicedirectory.hexago service ls
Flags:
-l: lists services line-by-line
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.
-
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
-
This command lists all applications under the
internal/domain/<domainname>/appdirectory.hexago app ls
Flags:
-l: lists applications line-by-line
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
-
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
-
This command lists all infrastructures under the
internal/infrastructuredirectory.hexago infra ls
Flags:
-l: lists infrastructures line-by-line
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/pkgand 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. -
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
-
This command lists all packages under the
internal/pkgor/pkgdirectory.hexago pkg ls # list internal packagesFlags:
-g: lists global packages-a: list both global and internal packages.-l: lists packages line-by-line
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.
-
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
-
This command lists all entry points under the
cmddirectory.hexago cmd ls
Flags:
-l: lists entry points line-by-line
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:
-e: run entry point with environment variable. You can use multiple environment variablehexago run <entry-point-name> -e <ENV_KEY1>=<ENV_VALUE1> -e <ENV_KEY2>=<ENV_VALUE2>
You can customize this run command with given entry point in
.hexago/config.yamlfile.You can specify all envs from
config.yamlfile 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 apicommand is executed as above, it starts theapientry point according to the settings in theconfig.yamlfile. -
As a second method, you can use the
runsub-command as an alternative to makefile. You can create a new entry in therunnerssection of the.hexago/config.yamlfile to call it with theruncommand.The special commands created do not need to have an entry point equivalent. We can add a special command using the
cmdkey.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-commandcommand, you will get the following result.go version go1.23.0 darwin/arm64
This command prints hexagonal structure of project.
hexago treeWhen 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: doIf you want to use another template other than these templates, you can create your own 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
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}}
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

















