Step by step to start

In this section, our goal is to experiance on how to use and how to works with gogen. As we know, the best learning way is by practising it. We will create a very simple application which use common gogen command that oftenly used.

This is an justlogin application that take a message from some source and publish it via the rest api. The requirement for this justlogin application are

assume that some users already registered in the system

user will input email and password via rest api POST /login

the system will check the existense of User 
by checking the email to data storage

if the email does not exist, 
	the system will return error message and case closed

if the email exist, 
	then the system will compare the password 

if the password does not match 
	the system will return error message and case closed

if the password is match 
	then system will create a token and return it back into user

with gogen, We need 7 step to accomplish this project

  1. create a domain
  2. create an entity
  3. create an usecase
  4. create a repository or service
  5. create a gateway
  6. create a controller
  7. create an application

So Let's start now.

Create a go mod

In every go project you need to have a go.mod file as a dependency manager for your code. Create a go mod is not part of gogen step.

Create your project directory first and change directory into it. In this case, lets name our project with justlogin

$ mkdir justlogin
$ cd my justlogin

Create your go mod file by this commands

$ go mod init justlogin
$ ls -al

  drwxr-xr-x   3 mir  staff    96 Nov  6 12:39 .
  drwxr-xr-x+ 65 mir  staff  2080 Nov  6 12:39 ..
  -rw-r--r--   1 mir  staff    27 Nov  6 11:04 go.mod

your go.mod will created under your directory. And now you are ready to use gogen command.


How to use gogen commands

Gogen works using CLI command. Gogen has many command to help you setup your boiler plate code. You don't have to remember all of it. Each time you forgot what command you should call, you can type this.

$ gogen

You will see this

Gogen v0.0.1
Try one of this command to learn how to use it
  gogen domain
  gogen usecase
  gogen entity
  gogen valueobject
  gogen valuestring
  gogen enum
  gogen repository
  gogen service
  gogen test
  gogen gateway
  gogen controller
  gogen error
  gogen application
  gogen crud

It will help remind you about which command you should call. In order to learn more detail about what the command can do, you can try run on of command.


gogen domain

Lets try our first command which is gogen domain

$ gogen domain

The output is

  # Initiate gogen project with default input. You may change later under .gogen folder
  gogen domain userauth
    'userauth' is a your domain name

That output is telling you about how to call the gogen domain command. In this case, gogen domain has 1 parameter which is a domain name. In above example the domain name is userauth

For our justlogin application, we will create a domain with name userauth.

$ gogen domain userauth

Now you see some folders and files are created under your justlogin project

$ ls -al

  drwxr-xr-x  11 mir  staff   352 Nov  6 11:27 .
  drwxr-xr-x+ 65 mir  staff  2080 Nov  6 11:27 ..
  -rw-r--r--   1 mir  staff    68 Nov  6 11:27 .gitignore
  drwxr-xr-x   3 mir  staff    96 Nov  6 11:27 .gogen
  -rw-r--r--   1 mir  staff   509 Nov  6 11:27 Dockerfile
  -rw-r--r--   1 mir  staff    14 Nov  6 11:27 README.md
  -rw-r--r--   1 mir  staff   692 Nov  6 11:27 config.json
  -rw-r--r--   1 mir  staff   692 Nov  6 11:27 config.sample.json
  -rw-r--r--   1 mir  staff   662 Nov  6 11:27 docker-compose.yml
  drwxr-xr-x   3 mir  staff    96 Nov  6 11:27 domain_userauth
  -rw-r--r--   1 mir  staff    27 Nov  6 11:04 go.mod

Those files are common file needed for running some application. In this case, gogen prepare it for you. But we still cannot run anything right now. We need another command.

to learn more about what is domain, you may go to this article details (link will added later)


gogen entity

The next step is creating an data model for our justlogin apps. In gogen our data model stored in a entity. Let see how to use gogen entity command.

try to call

$ gogen entity

The output

  # Create an entity
  gogen entity Product
    'Product' is an entity name

This command has one parameter which is an entity name

Since we want to store our user information, we will create entity with name User

$ gogen entity User

This command will create this structure

domain_userauth
├── README.md
└── model
    ├── entity
    │   └── user.go
    └── vo
        └── user_id.go

domain_userauth/model/entity/user.go

package entity

import (
	"justlogin/domain_userauth/model/vo"
	"time"
)

type User struct {
	ID      vo.UserID `bson:"_id" json:"id"`
	Created time.Time `bson:"created" json:"created"`
	Updated time.Time `bson:"updated" json:"updated"`

	// edit or add new necessary field here ...
}

type UserCreateRequest struct {
	RandomString string    `json:"-"`
	Now          time.Time `json:"-"`

	// edit or add new necessary field for create request here ...

}

func (r UserCreateRequest) Validate() error {

	// validate the create request here ...

	return nil
}

func NewUser(req UserCreateRequest) (*User, error) {

	err := req.Validate()
	if err != nil {
		return nil, err
	}

	id, err := vo.NewUserID(req.RandomString, req.Now)
	if err != nil {
		return nil, err
	}

	var obj User
	obj.ID = id
	obj.Created = req.Now
	obj.Updated = req.Now

	// another field input here ...

	return &obj, nil
}

type UserUpdateRequest struct {
	Now time.Time `json:"-"`

	// add new necessary field to update request here ...

}

func (r UserUpdateRequest) Validate() error {

	// validate the update request here ...

	return nil
}

func (r *User) Update(req UserUpdateRequest) error {

	err := req.Validate()
	if err != nil {
		return err
	}

	r.Updated = req.Now

	// update field here ...

	return nil
}

and domain_userauth/model/vo/user_id.go

package vo

import (
	"fmt"
	"time"
)

type UserID string

func NewUserID(randomStringID string, now time.Time) (UserID, error) {  
	var obj = UserID(fmt.Sprintf("OBJ-%s-%s", now.Format("060102"), randomStringID))

  // you may change it as necessary

	return obj, nil
}

func (r UserID) String() string {
	return string(r)
}

Let add some new fields Email, and Password into User struct in domain_userauth/model/entity/user.go

type User struct {
	ID       vo.UserID `bson:"_id" json:"id"`
	Created  time.Time `bson:"created" json:"created"`
	Updated  time.Time `bson:"updated" json:"updated"`
	Email    string    `bson:"email" json:"email"`
	Password string    `bson:"password" json:"-"`
}

At the Password field, we use tag json:"-" since we don't want to expose it.

Still in domain_userauth/model/entity/user.go, Add new method for checking the password

func (u User) PasswordIsCorrect(passwordFromInput string) bool {
	return u.Password == passwordFromInput
}

Disclaimers! For now, we are not using any encryption algorithm for the password for sake of simplicity. In real world project we must use the encryption like a brcypt.


gogen usecase

Lets try our next command which is gogen usecase

$ gogen usecase

The output is

  # Create a new usecase
  gogen usecase RunOrderCreate
    'RunOrderCreate' is an usecase name

This gogen usecase has one parameter which is the usecase name. This above example the usecase name is RunOrderCreate. You may wonder why the name of usecase is really weird. That is because we have a conventionfor creating a name. We will cover it deeply later in another article.

Since we want to do the login, we may name it RunUserLogin

$ gogen usecase RunUserLogin

After call gogen usecase we got

domain_userauth
└── usecase
    └── runuserlogin
        ├── README.md
        ├── inport.go
        ├── interactor.go
        └── outport.go

Lets try to take a look at those files.

domain_userauth/usecase/runuserlogin/inport.go

package runuserlogin

import "justlogin/shared/gogen"

type Inport gogen.Inport[InportRequest, InportResponse]

type InportRequest struct {
}

type InportResponse struct {
}

Inport is a place to define request and response for running a usecase.

domain_userauth/usecase/runuserlogin/interactor.go

package runuserlogin

import (
	"context"
)

type runUserLoginInteractor struct {
	outport Outport
}

func NewUsecase(outputPort Outport) Inport {
	return &runUserLoginInteractor{
		outport: outputPort,
	}
}

func (r *runUserLoginInteractor) Execute(ctx context.Context, req InportRequest) (*InportResponse, error) {

	res := &InportResponse{}

	// code your usecase definition here ...
	//!

	return res, nil
}

Interactor is a place to define the code logic to run a usecase

domain_userauth/usecase/runuserlogin/outport.go

package runuserlogin

type Outport interface {
}

Outport is the place to define function that required by usecase to accessing external system

Lets modify some code. Under domain_userauth/usecase/runuserlogin/inport.go adding some needed field to InportRequest and InportResponse struct

type InportRequest struct {
  Email    string
	Password string
}

type InportResponse struct {
  Token string
}

gogen repository

Now. We want to create a repository to find a user by the email from some external source

try call

$ gogen repository

The output is

  # Create a repository and inject the template code into interactor file with '//!' flag
  gogen repository SaveOrder Order RunOrderCreate
    'SaveOrder'   is a repository func name
    'Order'       is an entity name
    'RunOrderCreate' is an usecase name

  # Create a repository without inject the template code into usecase
  gogen repository SaveOrder Order
    'SaveOrder' is a repository func name
    'Order'     is an entity name

Now you see that we have 2 kind of repository command. For now we will try the first command. The second command actually do the same thing but without code injection into interactor and outport. Lets try the first gogen command.

$ gogen repository FindOneUserByEmail User RunUserLogin

Now we just add new folder repository

└── domain_userauth
    └── model
        └── repository
            └── repository.go

In this case, we just create a repository with this specification

  1. Create the interface with name FindOneUserByEmailRepository which has one method on it with name FindOneUserByEmail
  2. Use the existing entity User (or create the new one if it not exist) in method FindOneUserByEmail
  3. Inject the method in the usecase RunUserLogin

This command create some folders and files like

  • domain_userauth/model/entity/user.go (if not exist)
  • domain_userauth/model/repository/repository.go (by append the interface if it never exist)
  • domain_userauth/model/vo/user_id.go (if not exist)

and also inject some code into

  • domain_userauth/usecase/runuserlogin/interactor.go
  • domain_userauth/usecase/runuserlogin/outport.go

Lets take a look at domain_userauth/model/repository/repository.go

package repository

import (
	"context"
	"justlogin/domain_userauth/model/entity"
)

type FindOneUserByEmailRepo interface {
	FindOneUserByEmail(ctx context.Context, userID string) (*entity.User, error)
}

If you open the domain_userauth/usecase/runuserlogin/interactor.go we have new additional code injected to the method Execute with basic template for FindOne method.

func (r *runUserLoginInteractor) Execute(ctx context.Context, req InportRequest) (*InportResponse, error) {

	res := &InportResponse{}

	// code your usecase definition here ...

	userObj, err := r.outport.FindOneUserByEmail(ctx, "userID")
	if err != nil {
		return nil, err
	}
	if userObj == nil {
		return nil, fmt.Errorf("object not found")
	}

	//!

	return res, nil
}

Code injection works by finding the special charachter //! and replace it by proper template

Also in domain_userauth/usecase/runuserlogin/outport.go

package runuserlogin

import "justlogin/domain_userauth/model/repository"

type Outport interface {
	repository.FindOneUserByEmailRepo
}

Here we use interface composition. In other words, now Outport has the method FindOneUserByEmail

Modify the method FindOneUserByEmail by rename the second parameters from userID to email

type FindOneUserByEmailRepo interface {
	FindOneUserByEmail(ctx context.Context, email string) (*entity.User, error)
}

in interactor, lets modify some code

  • pass the email from InportRequest email field to the method FindOneUserByEmail
  • call the method PasswordIsCorrect from returned obj for checking the password
  • fix the wording on error message to the proper message
func (r *runUserLoginInteractor) Execute(ctx context.Context, req InportRequest) (*InportResponse, error) {

	res := &InportResponse{}

	// code your usecase definition here ...

	userObj, err := r.outport.FindOneUserByEmail(ctx, req.Email)
	if err != nil {
		return nil, err
	}

	if userObj == nil {
		return nil, fmt.Errorf("invalid email or password")
	}

	if !userObj.PasswordIsCorrect(req.Password) {
		return nil, fmt.Errorf("invalid email or password")
	}

	//!

	return res, nil
}

gogen service

Now lets add some service to generate the random unique token. We will use gogen service. Lets try now

$ gogen service

Output

	# Create a service and inject the template code into interactor file with '//!' flag
	gogen service PublishMessage RunOrderCreate
		'PublishMessage' is a service func name
		'RunOrderCreate' is an usecase name

	# Create a service without inject the template code into usecase
	gogen service PublishMessage
		'PublishMessage' is a service func name

Service is almost similar with repository, But repository is commonly used for accessing data storage like database. Accessing can be like create, update, delete or query the data. But Service is more common purpose. Service commonly used for generate some undeterministic value, call other service, sending the event signal, or do some logic calculation that involve the entity or value object. Service can be an interface or struct. By default, gogen will create the service as interface.

Let create our service to generate the random unique token using first gogen service command

$ gogen service GenerateLoginToken RunUserLogin

We just add new folder service

└── domain_userauth
    └── model
        └── service
            └── service.go

The content of domain_userauth/model/service/service.go

package service

import "context"

type GenerateLoginTokenService interface {
	GenerateLoginToken(ctx context.Context, req GenerateLoginTokenServiceRequest) (*GenerateLoginTokenServiceResponse, error)
}

type GenerateLoginTokenServiceRequest struct {
}

type GenerateLoginTokenServiceResponse struct {
}

and some code is injected in interactor and outport

domain_userauth/usecase/runuserlogin/interactor.go

func (r *runUserLoginInteractor) Execute(ctx context.Context, req InportRequest) (*InportResponse, error) {

	res := &InportResponse{}

	// code your usecase definition here ...

	userObj, err := r.outport.FindOneUserByEmail(ctx, req.Email)
	if err != nil {
		return nil, err
	}

	if userObj == nil {
		return nil, fmt.Errorf("invalid email or password")
	}

	if !userObj.PasswordIsCorrect(req.Password) {
		return nil, fmt.Errorf("invalid email or password")
	}

	generateLoginTokenResponse, err := r.outport.GenerateLoginToken(ctx, service.GenerateLoginTokenServiceRequest{})
	if err != nil {
		return nil, err
	}

	_ = generateLoginTokenResponse

	//!

	return res, nil
}

domain_userauth/usecase/runuserlogin/outport.go

package runuserlogin

import (
	"justlogin/domain_userauth/model/repository"
	"justlogin/domain_userauth/model/service"
)

type Outport interface {
	repository.FindOneUserByEmailRepo
	service.GenerateLoginTokenService
}

We can simplify the service since no parameter needed and we just want to return the string as a token. So we can edit the code in domain_userauth/model/service/service.go

package service

import "context"

type GenerateLoginTokenService interface {
	GenerateLoginToken(ctx context.Context) string
}

and also edit the interactor

func (r *runUserLoginInteractor) Execute(ctx context.Context, req InportRequest) (*InportResponse, error) {

	res := &InportResponse{}

	userObj, err := r.outport.FindOneUserByEmail(ctx, req.Email)
	if err != nil {
		return nil, err
	}

	if userObj == nil {
		return nil, fmt.Errorf("invalid email or password")
	}

	if !userObj.PasswordIsCorrect(req.Password) {
		return nil, fmt.Errorf("invalid email or password")
	}

	token := r.outport.GenerateLoginToken(ctx)

	res.Token = token

	return res, nil
}

gogen gateway

As we created before we are using an interface for find a user by email and generate a token. Now we need to implement the interface. We can perform this action by call the gogen gateway command.

$ gogen gateway

The output

	# Create a gateway for all usecases with cloverdb sample implementation
	gogen gateway inmemory
		'inmemory' is a gateway name

	# Create a gateway for specific usecase
	gogen gateway inmemory cloverdb
		'inmemory' is a gateway name
		'cloverdb' is a sample implementation

	# Create a gateway for specific usecase
	gogen gateway inmemory cloverdb CreateOrder
		'inmemory'    is a gateway name
		'cloverdb' is a sample implementation
		'CreateOrder' is an usecase name

There are 3 type off command. We are gonna use the first one. In this case we will use simple hardcoded implementation without using any database. So we name it as hardcoded

$ gogen gateway hardcoded

Now we add new folder gateway/hardcoded

└── domain_userauth
    └── gateway
        └── hardcoded
            └── gateway.go

This command produce domain_userauth/gateway/hardcoded/gateway.go

package hardcoded

import (
	"context"
	"justlogin/domain_userauth/model/entity"
	"justlogin/shared/gogen"
	"justlogin/shared/infrastructure/config"
	"justlogin/shared/infrastructure/logger"
)

type gateway struct {
	log     logger.Logger
	appData gogen.ApplicationData
	config  *config.Config
}

// NewGateway ...
func NewGateway(log logger.Logger, appData gogen.ApplicationData, cfg *config.Config) *gateway {

	return &gateway{
		log:     log,
		appData: appData,
		config:  cfg,
	}
}

func (r *gateway) FindOneUserByEmail(ctx context.Context, userID string) (*entity.User, error) {
	r.log.Info(ctx, "called")

	return nil, nil
}

func (r *gateway) GenerateLoginToken(ctx context.Context) string {
	r.log.Info(ctx, "called")

	return ""
}

Lets implement very simple implementation for both method

func (r *gateway) FindOneUserByEmail(ctx context.Context, userID string) (*entity.User, error) {
	r.log.Info(ctx, "called")

	if userEmail == "userone@gmail.com" {
		return &entity.User{
			ID:       vo.UserID("USR1"),
			Created:  time.Now(),
			Updated:  time.Now(),
			Email:    "userone@gmail.com",
			Password: "111",
		}, nil
	}

	if userEmail == "usertwo@gmail.com" {
		return &entity.User{
			ID:       vo.UserID("USR2"),
			Created:  time.Now(),
			Updated:  time.Now(),
			Email:    "usertwo@gmail.com",
			Password: "222",
		}, nil
	}

	return nil, nil
}

func (r *gateway) GenerateLoginToken(ctx context.Context) string {
	r.log.Info(ctx, "called")

	return "ABC123"
}

gogen controller

Now lets create a controller. with gogen we can use gogen controller command.

$ gogen controller

Output

	# Create a controller for all usecases using gin as default web framework
	gogen controller restapi
		'restapi' is a gateway name

	# Create a controller with for all usecases with selected framework
	You may try the other one like : echo, gin, gorilla, nethttp, rabbitmq, rpc, simple
	in this example we use 'echo'
	gogen controller restapi echo
		'restapi'     is a gateway name
		'CreateOrder' is an usecase name

	# Create a controller with defined web framework and specific usecase
	gogen controller restapi gin CreateOrder
		'restapi'      is a gateway name
		'gin'          is a sample webframework.
		'CreateOrder'  is an usecase name

There are 3 types of gogen controller command and we are gonna use the first type of command

$ gogen controller restapi

it will create the some folder and files

  • domain_userauth/controller/restapi/handler_runuserlogin.go
  • domain_userauth/controller/restapi/http_runuserlogin.http
  • domain_userauth/controller/restapi/interceptor.go
  • domain_userauth/controller/restapi/router.go
└── domain_userauth
    └── controller
        └── restapi
            ├── handler_runuserlogin.go
            ├── http_runuserlogin.http
            ├── interceptor.go
            └── router.go

Let see how the domain_userauth/controller/restapi/router.go looks like

package restapi

import (
	"justlogin/shared/gogen"
	"justlogin/shared/infrastructure/config"
	"justlogin/shared/infrastructure/logger"
	"justlogin/shared/infrastructure/token"

	"github.com/gin-gonic/gin"
)

type selectedRouter = gin.IRouter

type ginController struct {
	*gogen.BaseController
	log      logger.Logger
	cfg      *config.Config
	jwtToken token.JWTToken
}

func NewGinController(log logger.Logger, cfg *config.Config, tk token.JWTToken) gogen.RegisterRouterHandler[selectedRouter] {
	return &ginController{
		BaseController: gogen.NewBaseController(),
		log:            log,
		cfg:            cfg,
		jwtToken:       tk,
	}
}

func (r *ginController) RegisterRouter(router selectedRouter) {

	resource := router.Group("/api/v1", r.authentication())
	resource.POST("/runuserlogin", r.authorization(), r.runUserLoginHandler())

}

And domain_userauth/controller/restapi/handler_runuserlogin.go looks like

package restapi

import (
	"context"
	"justlogin/domain_userauth/usecase/runuserlogin"
	"justlogin/shared/gogen"
	"justlogin/shared/infrastructure/logger"
	"justlogin/shared/model/payload"
	"justlogin/shared/util"
	"net/http"

	"github.com/gin-gonic/gin"
)

func (r *ginController) runUserLoginHandler() gin.HandlerFunc {

	type InportRequest = runuserlogin.InportRequest
	type InportResponse = runuserlogin.InportResponse

	inport := gogen.GetInport[InportRequest, InportResponse](r.GetUsecase(InportRequest{}))

	type request struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	type response struct {
		Token string `json:"token"`
	}

	return func(c *gin.Context) {

		traceID := util.GenerateID(16)

		ctx := logger.SetTraceID(context.Background(), traceID)

		var jsonReq request
		if err := c.BindJSON(&jsonReq); err != nil {
			r.log.Error(ctx, err.Error())
			c.JSON(http.StatusBadRequest, payload.NewErrorResponse(err, traceID))
			return
		}

		var req InportRequest
		req.Email = jsonReq.Email
		req.Password = jsonReq.Password

		r.log.Info(ctx, util.MustJSON(req))

		res, err := inport.Execute(ctx, req)
		if err != nil {
			r.log.Error(ctx, err.Error())
			c.JSON(http.StatusBadRequest, payload.NewErrorResponse(err, traceID))
			return
		}

		var jsonRes response
		jsonRes.Token = res.Token

		r.log.Info(ctx, util.MustJSON(jsonRes))
		c.JSON(http.StatusOK, payload.NewSuccessResponse(jsonRes, traceID))

	}
}

Lets change the router a little bit from /runuserlogin to be /login

func (r *ginController) RegisterRouter(router selectedRouter) {

	resource := router.Group("/api/v1", r.authentication())
	resource.POST("/login", r.authorization(), r.runUserLoginHandler())

}

gogen application

And the last step is we are creating the application

$ gogen application

Output

   # Create a application for all controller
   gogen application appone
     'appone'  is an application name

   # Create a application for specific controller
   gogen application appone restapi
     'appone'  is an application name
     'restapi' is a controller name

   # Create a application for specific controller and gateway
   gogen application appone restapi prod
     'appone'  is an application name
     'restapi' is a controller name
     'prod'    is a gateway name

We have 3 type of gogen application command. We are gonna use the first one

$ gogen application myapp

This create new folder application and main file

├── application
│   └── app_myapp.go
└── main.go

And now gogen create us application/myapp.go that combine the usecase, gateway and controller together

package application

import (
	"justlogin/domain_userauth/controller/restapi"
	"justlogin/domain_userauth/gateway/hardcoded"
	"justlogin/domain_userauth/usecase/runuserlogin"
	"justlogin/shared/gogen"
	"justlogin/shared/infrastructure/config"
	"justlogin/shared/infrastructure/logger"
	"justlogin/shared/infrastructure/server"
	"justlogin/shared/infrastructure/token"
)

type myapp struct{}

func NewMyapp() gogen.Runner {
	return &myapp{}
}

func (myapp) Run() error {

	const appName = "myapp"

	cfg := config.ReadConfig()

	appData := gogen.NewApplicationData(appName)

	log := logger.NewSimpleJSONLogger(appData)

	jwtToken := token.NewJWTToken(cfg.JWTSecretKey)

	datasource := hardcoded.NewGateway(log, appData, cfg)

	httpHandler := server.NewGinHTTPHandler(log, cfg.Servers[appName].Address, appData)

	x := restapi.NewGinController(log, cfg, jwtToken)
	x.AddUsecase(
		//
		runuserlogin.NewUsecase(datasource),
	)
	x.RegisterRouter(httpHandler.Router)

	httpHandler.RunWithGracefullyShutdown()

	return nil
}

main.go

package main

import (
	"flag"
	"fmt"
	"justlogin/application"
	"justlogin/shared/gogen"
)

var Version = "0.0.1"

func main() {
	appMap := map[string]gogen.Runner{
		//
		"myapp": application.NewMyapp(),
	}

	flag.Parse()

	app, exist := appMap[flag.Arg(0)]
	if !exist {
		fmt.Println("You may try 'go run main.go <app_name>' :")
		for appName := range appMap {
			fmt.Printf(" - %s\n", appName)
		}
		return
	}

	fmt.Printf("Version %s\n", Version)
	err := app.Run()
	if err != nil {
		return
	}

}

For now, nothing need to be updated anymore. Your application is ready. Try to run

$ go run main.go

If the output is

shared/infrastructure/server/http_server_gin.go:7:2: no required module provides package github.com/gin-contrib/cors; to add it:
        go get github.com/gin-contrib/cors
domain_userauth/controller/restapi/handler_runuserlogin.go:12:2: no required module provides package github.com/gin-gonic/gin; to add it:
        go get github.com/gin-gonic/gin
shared/infrastructure/token/token.go:8:2: no required module provides package github.com/golang-jwt/jwt; to add it:
        go get github.com/golang-jwt/jwt
shared/util/id.go:3:8: no required module provides package github.com/matoous/go-nanoid; to add it:
        go get github.com/matoous/go-nanoid
shared/gogen/test_scenario.go:7:2: no required module provides package github.com/stretchr/testify/assert; to add it:
        go get github.com/stretchr/testify/assert

This is because you need to run go mod tidy to download some go dependency

$ go mod tidy

Then run again

$ go run main.go

you will see the output like this

You may try 'go run main.go <app_name>' :
 - myapp

This is because gogen support multiple application in one project. You need call the specific application. In this case, you may try run

$ go run main.go myapp

The output will be

Version 0.0.1
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> justlogin/shared/infrastructure/server.NewGinHTTPHandler.func1 (3 handlers)
[GIN-debug] POST   /api/v1/login      --> justlogin/domain_userauth/controller/restapi.(*ginController).runUserLoginHandler.func1 (6 handlers)
{"appName":"myapp","appInstID":"17IW","start":"2022-11-23 09:50:48","severity":"INFO","message":"0000000000000000 server is running at :8000","location":"server.(*GracefullyShutdown).RunWithGracefullyShutdown:40","time":"2022-11-23 09:50:48"}

Now test the API by running the curl or you may use postman

$ curl -X POST -d '{"email": "userone@gmail.com", "password": "111"}' http://localhost:8000/api/v1/login

output

{"success":true,"errorCode":"","errorMessage":"","data":{"token":"ABC123"},"traceId":"NH1PH1Z02O1IT1YC"}

And congratulation your application is running!

Last Updated:
Contributors: mirza