Accelerate Go Projects with Mix‑Go: Build CLI, API, Web, gRPC & Worker Pools Fast

This guide introduces Mix‑Go, a Go‑based rapid‑development framework that provides interactive scaffolding, command‑line prototyping, and a DI/IoC container, and walks through installing the tool, generating project skeletons, and creating functional CLI, API, Web, WebSocket, gRPC services and a worker‑pool queue consumer with complete code examples.

Go Development Architecture Practice
Go Development Architecture Practice
Go Development Architecture Practice
Accelerate Go Projects with Mix‑Go: Build CLI, API, Web, gRPC & Worker Pools Fast

Mix Go Overview

Mix Go is a rapid‑development framework for Go, similar to Vue CLI for front‑end projects. It provides:

Interactive scaffolding via mix-go/mixcli that can generate CLI, API, Web, and gRPC projects.

Generated code works out‑of‑the‑box with optional .env and configuration files ( .yml, .json, .toml).

Choice of database libraries (Gorm, Xorm) and logging libraries (Logrus, Zap).

Additional modules: mix-go/xcli for command‑line prototyping and mix-go/xdi for a DI/IoC container.

Quick Start

Install the scaffolding tool and create a new project:

go get github.com/mix-go/mixcli
mixcli new hello

The wizard lets you select the project type (CLI, API, Web, gRPC). The generated directory layout is:

.
├── README.md
├── bin
├── commands
├── conf
├── configor
├── di
├── dotenv
├── go.mod
├── go.sum
├── main.go
└── ... (type‑specific folders)

CLI Application

Example main.go registers commands via xcli.AddCommand:

package main

import (
    "github.com/mix-go/cli-skeleton/commands"
    _ "github.com/mix-go/cli-skeleton/configor"
    _ "github.com/mix-go/cli-skeleton/di"
    _ "github.com/mix-go/cli-skeleton/dotenv"
    "github.com/mix-go/dotenv"
    "github.com/mix-go/xcli"
)

func main() {
    xcli.SetName("app").
        SetVersion("0.0.0-alpha").
        SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
    xcli.AddCommand(commands.Commands...).Run()
}

Command implementation ( commands/hello.go) uses xcli flag parsing:

package commands

import (
    "fmt"
    "github.com/mix-go/xcli/flag"
)

type HelloCommand struct{}

func (t *HelloCommand) Main() {
    name := flag.Match("n", "name").String("OpenMix")
    say := flag.Match("say").String("Hello, World!")
    fmt.Printf("%s: %s
", name, say)
}

Build and run:

# Linux/macOS
go build -o bin/go_build_main_go main.go
# Windows
go build -o bin/go_build_main_go.exe main.go

./bin/go_build_main_go hello --name=liujian --say=hello
# Output: liujian: hello

Worker‑Pool Queue Consumer

Mix‑Go provides mix-go/xwp for high‑concurrency background workers. A typical worker pool command ( commands/workerpool.go) looks like:

package commands

import (
    "context"
    "fmt"
    "github.com/mix-go/cli-skeleton/di"
    "github.com/mix-go/xwp"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"
)

type worker struct{ xwp.WorkerTrait }

func (t *worker) Do(data interface{}) {
    defer func() { if err := recover(); err != nil { di.Logrus().Error(err) } }()
    // business logic here
    // store result to DB
}

func NewWorker() xwp.Worker { return &worker{} }

type WorkerPoolDaemonCommand struct{}

func (t *WorkerPoolDaemonCommand) Main() {
    redis := globals.Redis()
    jobQueue := make(chan interface{}, 50)
    d := xwp.NewDispatcher(jobQueue, 15, NewWorker)

    ch := make(chan os.Signal)
    signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
    go func() { <-ch; d.Stop() }()

    go func() {
        for {
            res, err := redis.BRPop(context.Background(), 3*time.Second, "foo").Result()
            if err != nil {
                if strings.Contains(err.Error(), "redis: nil") { continue }
                fmt.Printf("Redis Error: %s
", err)
                d.Stop(); return
            }
            jobQueue <- res[1]
        }
    }()
    d.Run() // blocks until all jobs are processed
}

API Service

Generate an API skeleton (select API in the wizard). The entry point registers commands similarly to the CLI example:

package main

import (
    "github.com/mix-go/api-skeleton/commands"
    _ "github.com/mix-go/api-skeleton/configor"
    _ "github.com/mix-go/api-skeleton/di"
    _ "github.com/mix-go/api-skeleton/dotenv"
    "github.com/mix-go/dotenv"
    "github.com/mix-go/xcli"
)

func main() {
    xcli.SetName("app").
        SetVersion("0.0.0-alpha").
        SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
    xcli.AddCommand(commands.Commands...).Run()
}

API command ( commands/api.go) starts a Gin server, reads configuration from .env, and handles graceful shutdown:

package commands

import (
    "context"
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/mix-go/api-skeleton/di"
    "github.com/mix-go/api-skeleton/routes"
    "github.com/mix-go/dotenv"
    "github.com/mix-go/xcli/flag"
    "github.com/mix-go/xcli/process"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"
)

type APICommand struct{}

func (t *APICommand) Main() {
    if flag.Match("d", "daemon").Bool() { process.Daemon() }
    logger := di.Logrus()
    server := di.Server()
    addr := dotenv.Getenv("GIN_ADDR").String(":8080")
    mode := dotenv.Getenv("GIN_MODE").String(gin.ReleaseMode)
    gin.SetMode(mode)
    router := gin.New()
    routes.SetRoutes(router)
    server.Addr = flag.Match("a", "addr").String(addr)
    server.Handler = router

    ch := make(chan os.Signal)
    signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-ch
        logger.Info("Server shutdown")
        ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
        if err := server.Shutdown(ctx); err != nil { logger.Errorf("Server shutdown error: %s", err) }
    }()

    if mode != gin.ReleaseMode {
        loggerFunc := gin.LoggerWithConfig(gin.LoggerConfig{Formatter: func(p gin.LogFormatterParams) string {
            return fmt.Sprintf("%s|%s|%d|%s", p.Method, p.Path, p.StatusCode, p.ClientIP)
        }, Output: logger.Out})
        router.Use(loggerFunc)
    }
    logger.Infof("Server start at %s", server.Addr)
    if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") { panic(err) }
}

Routes are defined in routes/main.go and include a simple /hello endpoint and a /users/add POST endpoint.

Web Service

The Web skeleton is similar to the API one but adds static file serving and HTML template loading. The command ( commands/web.go) loads templates from ../templates/* and static assets from ../public/static:

package commands

import (
    "context"
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/mix-go/dotenv"
    "github.com/mix-go/web-skeleton/di"
    "github.com/mix-go/web-skeleton/routes"
    "github.com/mix-go/xcli"
    "github.com/mix-go/xcli/flag"
    "github.com/mix-go/xcli/process"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"
)

type WebCommand struct{}

func (t *WebCommand) Main() {
    if flag.Match("d", "daemon").Bool() { process.Daemon() }
    logger := di.Logrus()
    server := di.Server()
    addr := dotenv.Getenv("GIN_ADDR").String(":8080")
    mode := dotenv.Getenv("GIN_MODE").String(gin.ReleaseMode)
    gin.SetMode(mode)
    router := gin.New()
    routes.SetRoutes(router)
    server.Addr = flag.Match("a", "addr").String(addr)
    server.Handler = router

    ch := make(chan os.Signal)
    signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
    go func() { <-ch; logger.Info("Server shutdown"); ctx, _ := context.WithTimeout(context.Background(), 10*time.Second); server.Shutdown(ctx) }()

    if mode != gin.ReleaseMode {
        loggerFunc := gin.LoggerWithConfig(gin.LoggerConfig{Formatter: func(p gin.LogFormatterParams) string {
            return fmt.Sprintf("%s|%s|%d|%s", p.Method, p.Path, p.StatusCode, p.ClientIP)
        }, Output: logger.Out})
        router.Use(loggerFunc)
    }

    router.LoadHTMLGlob(fmt.Sprintf("%s/../templates/*", xcli.App().BasePath))
    router.Static("/static", fmt.Sprintf("%s/../public/static", xcli.App().BasePath))
    router.StaticFile("/favicon.ico", fmt.Sprintf("%s/../public/favicon.ico", xcli.App().BasePath))

    logger.Infof("Server start at %s", server.Addr)
    if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") { panic(err) }
}

WebSocket Service

The Web skeleton registers a /websocket route. The controller upgrades the HTTP request to a WebSocket connection and manages a session with read/write goroutines:

package controllers

import (
    "github.com/gin-gonic/gin"
    "github.com/gorilla/websocket"
    "github.com/mix-go/web-skeleton/di"
    "github.com/mix-go/xcli"
    "net/http"
)

var upgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}

type WebSocketController struct{}

func (t *WebSocketController) Index(c *gin.Context) {
    logger := di.Logrus()
    if xcli.App().Debug { upgrader.CheckOrigin = func(r *http.Request) bool { return true } }
    conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil { logger.Error(err); c.Status(http.StatusInternalServerError); c.Abort(); return }
    session := WebSocketSession{Conn: conn, Header: c.Request.Header, Send: make(chan []byte, 100)}
    session.Start()
    di.Server().RegisterOnShutdown(func() { session.Stop() })
    logger.Infof("Upgrade: %s", c.Request.UserAgent())
}

type WebSocketSession struct{ Conn *websocket.Conn; Header http.Header; Send chan []byte }

func (t *WebSocketSession) Start() {
    go func() {
        logger := di.Logrus()
        for {
            mt, msg, err := t.Conn.ReadMessage()
            if err != nil {
                if !websocket.IsCloseError(err, 1001, 1006) { logger.Error(err) }
                t.Stop(); return
            }
            if mt != websocket.TextMessage { continue }
            handler := WebSocketHandler{Session: t}
            handler.Index(msg)
        }
    }()
    go func() {
        logger := di.Logrus()
        for msg := range t.Send {
            if err := t.Conn.WriteMessage(websocket.TextMessage, msg); err != nil { logger.Error(err); t.Stop(); return }
        }
    }()
}

func (t *WebSocketSession) Stop() {
    defer func() { if r := recover(); r != nil { di.Logrus().Error(r) } }()
    close(t.Send)
    _ = t.Conn.Close()
}

type WebSocketHandler struct{ Session *WebSocketSession }

func (h *WebSocketHandler) Index(msg []byte) { h.Session.Send <- []byte("hello, world!") }

gRPC Service and Client

Generate a gRPC skeleton (select gRPC ). Define the protobuf in protos/user.proto:

syntax = "proto3";
package go.micro.grpc.user;
option go_package = ".;protos";

service User {
    rpc Add(AddRequest) returns (AddResponse) {}
}

message AddRequest { string Name = 1; }
message AddResponse { int32 error_code = 1; string error_message = 2; int64 user_id = 3; }

Compile the proto:

cd protos
protoc --go_out=plugins=grpc:. user.proto

Server command ( commands/server.go) registers the service and handles graceful shutdown:

package commands

import (
    "github.com/mix-go/dotenv"
    "github.com/mix-go/grpc-skeleton/di"
    pb "github.com/mix-go/grpc-skeleton/protos"
    "github.com/mix-go/grpc-skeleton/services"
    "github.com/mix-go/xcli/flag"
    "github.com/mix-go/xcli/process"
    "google.golang.org/grpc"
    "net"
    "os"
    "os/signal"
    "strings"
    "syscall"
)

var listener net.Listener

type GrpcServerCommand struct{}

func (t *GrpcServerCommand) Main() {
    if flag.Match("d", "daemon").Bool() { process.Daemon() }
    addr := dotenv.Getenv("GIN_ADDR").String(":8080")
    logger := di.Logrus()
    var err error
    listener, err = net.Listen("tcp", addr)
    if err != nil { panic(err) }
    ch := make(chan os.Signal)
    signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
    go func() { <-ch; logger.Info("Server shutdown"); listener.Close() }()
    s := grpc.NewServer()
    pb.RegisterUserServer(s, &services.UserService{})
    logger.Infof("Server run %s", addr)
    if err := s.Serve(listener); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { panic(err) }
}

Service implementation ( services/user.go) contains business logic:

package services

import (
    "context"
    pb "github.com/mix-go/grpc-skeleton/protos"
)

type UserService struct{}

func (t *UserService) Add(ctx context.Context, in *pb.AddRequest) (*pb.AddResponse, error) {
    // placeholder for DB operation
    resp := pb.AddResponse{ErrorCode: 0, ErrorMessage: "", UserId: 10001}
    return &resp, nil
}

Client command ( commands/client.go) dials the server, sends an Add request and prints the returned user ID:

package commands

import (
    "context"
    "fmt"
    "github.com/mix-go/dotenv"
    pb "github.com/mix-go/grpc-skeleton/protos"
    "google.golang.org/grpc"
    "time"
)

type GrpcClientCommand struct{}

func (t *GrpcClientCommand) Main() {
    addr := dotenv.Getenv("GIN_ADDR").String(":8080")
    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil { panic(err) }
    defer conn.Close()
    cli := pb.NewUserClient(conn)
    req := pb.AddRequest{Name: "xiaoliu"}
    resp, err := cli.Add(ctx, &req)
    if err != nil { panic(err) }
    fmt.Printf("Add User: %d
", resp.UserId)
}

Dependency Injection (DI) Container Usage

Common components are defined in the di package and can be accessed anywhere, typically inside a command's Main method:

// Logger (Logrus or Zap)
logger := di.Logrus()
logger.Info("test")

// Gorm database
db := di.Gorm()
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user)
fmt.Println(result)

// Redis client (go‑redis)
rdb := di.GoRedis()
val, err := rdb.Get(context.Background(), "key").Result()
if err != nil { panic(err) }
fmt.Println("key", val)

Repository Links

Official source code mirrors:

https://github.com/mix-go/mix

https://gitee.com/mix-go/mix

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CLIGogRPCWebSocketAPIWorker PoolMix Go
Go Development Architecture Practice
Written by

Go Development Architecture Practice

Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.