Mir is a toolkit to develop RESTful API backend service like develop service of gRPC. It adapt some HTTP framework sush as Gin, Chi, Hertz, Echo, Iris, Fiber, Macaron, Mux, httprouter。
Generate a simple template project #
% go install github.com/alimy/mir/mirc/v4@latest
% mirc new -h
create template project
Usage:
mirc new [flags]
Flags:
-d, --dst string genereted destination target directory (default ".")
-h, --help help for new
--mir string mir replace package name or place
-p, --pkg string project's package name (default "github.com/alimy/mir-example")
-s, --style string generated engine style eg: gin,chi,mux,hertz,echo,iris,fiber,fiber-v2,macaron,httprouter (default "gin")
% mirc new -d example
% tree example
example
.
|-- Makefile
|-- README.md
|-- go.mod
|-- go.sum
|-- main.go
|-- mirc
| |-- auto
| | `-- api
| | |-- site.go
| | |-- v1
| | | `-- site.go
| | `-- v2
| | `-- site.go
| |-- gen.go
| `-- routes
| |-- site.go
| |-- v1
| | `-- site.go
| `-- v2
| `-- site.go
`-- servants
|-- core.go
|-- servants.go
|-- site.go
|-- site_v1.go
`-- site_v2.go
% cd example
% make generate
% make run
RESTful API define: #
// file: mirc/routes.go
package routes
import (
. "github.com/alimy/mir/v4"
. "github.com/alimy/mir/v4/engine"
)
func init() {
AddEntry(new(User))
}
type LoginReq struct {
Name string `json:"name"`
Passwd string `json:"passwd"`
}
type LoginResp struct {
JwtToken string `json:"jwt_token"`
}
// User user interface info
type User struct {
Chain Chain `mir:"-"`
Group Group `mir:"v1"`
Login func(Post, LoginReq) LoginResp `mir:"/login/"`
Logout func(Post) `mir:"/logout/"`
}
- Stub source code generatee automatic:
// file: mirc/auto/api/routes.go
// Code generated by go-mir. DO NOT EDIT.
// versions:
// - mir v4.0.0
package routes
import (
"net/http"
"github.com/alimy/mir/v4"
"github.com/gin-gonic/gin"
)
type _binding_ interface {
Bind(*gin.Context) mir.Error
}
type _render_ interface {
Render(*gin.Context)
}
type _default_ interface {
Bind(*gin.Context, any) mir.Error
Render(*gin.Context, any, mir.Error)
}
type LoginReq struct {
Name string `json:"name"`
Passwd string `json:"passwd"`
}
type LoginResp struct {
JwtToken string `json:"jwt_token"`
}
type User interface {
_default_
// Chain provide handlers chain for gin
Chain() gin.HandlersChain
Login(*gin.Context, *LoginReq) (*LoginResp, mir.Error)
Logout(*gin.Context) mir.Error
mustEmbedUnimplementedUserServant()
}
// UnimplementedUserServant can be embedded to have forward compatible implementations.
type UnimplementedUserServant struct {
}
// RegisterUserServant register User servant to gin
func RegisterUserServant(e *gin.Engine, s User) {
router := e.Group("v1")
// use chain for router
middlewares := s.Chain()
router.Use(middlewares...)
// register routes info to router
router.Handle("POST", "/login/", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req := new(LoginReq)
if err := s.Bind(c, req); err != nil {
s.Render(c, nil, err)
return
}
resp, err := s.Login(req)
s.Render(c, resp, err)
})
router.Handle("POST", "/logout/", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
r.Render(c, nil, s.Logout(c))
})
}
func (UnimplementedUserServant) Chain() gin.HandlersChain {
return nil
}
func (UnimplementedUserServant) Login(c *gin.Context, req *LoginReq) (*LoginResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedUserServant) Logout(c *gin.Context) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedUserServant) mustEmbedUnimplementedUserServant() {}
API interface implement: #
// file: servants/user.go
package servants
import (
"github.com/alimy/mir-example/v4/mirc/auto/api"
"github.com/alimy/mir/v4"
"github.com/gin-gonic/gin"
)
type baseSrv struct{}
func (baseSrv) Bind(c *gin.Context, obj any) mir.Error {
if err := c.ShouldBind(obj); err != nil {
mir.NewError(http.StatusBadRequest, err)
}
return nil
}
func (baseSrv) Render(c *gin.Context, data any, err mir.Error) {
if err == nil {
c.JSON(http.StatusOK, data)
} else {
c.JSON(err.StatusCode(), err.Error())
}
}
type userSrv struct {
baseSrv
api.UnimplementedUserServant
}
func newUserSrv() api.Site {
return &userSrv{}
}
Service register: #
// file: servants/servants.go
package servants
import (
"github.com/alimy/mir-example/v4/mirc/auto/api"
"github.com/gin-gonic/gin"
)
// RegisterServants register all the servants to gin.Engine
func RegisterServants(e *gin.Engine) {
api.RegisterUserServant(e, newUserSrv())
// TODO: some other servant to register
}
App start: #
// file: main.go
package main
import (
"log"
"github.com/alimy/mir-example/v4/servants"
"github.com/gin-gonic/gin"
)
func main() {
e := gin.Default()
// register servants to gin
servants.RegisterServants(e)
// start servant service
if err := e.Run(); err != nil {
log.Fatal(err)
}
}