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.
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 helloThe 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: helloWorker‑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.protoServer 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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
