Standard HTTP and GRPC Go Project Layout with Protobuf and GORM.
- HTTP API
- GRPC API
- GORM based MySQL
- JWT Generation
- Full Sample Code for Beginners
-
You need a MySQL Server, go to schema folder to apply all scripts to your MySQL. (or change the code delete all DAO related code.)
-
Prepare Protobuf API generation by run
make api_dep_install
-
Generate APIs by run
make api_gen
-
Run this project in GoLand or build it by
make build
then runmake run
to start the project. (for different OS and ARCH you may need change GOOS and GOARCH envs, maybe this link can help: https://www.digitalocean.com/community/tutorials/how-to-build-go-executables-for-multiple-platforms-on-ubuntu-16-04) -
Visit http://localhost:8081/api/v1/project/swagger, you can test the APIs.
│ .env # if you want to use JWT, store an env like: JWT_SECRET=golang in this file, it will not be submit to git.
│ .gitignore
│ go.mod
│ go.sum
│ LICENSE
│ Makefile
│ README.md # this file
│
├─api # protobuf and api definition
│ ├─general
│ │ └─v1
│ │ demo.proto
│ │
│ └─istiofy
│ └─v1
│ istiofy.proto
│ README.md
│
├─cmd # project start from here
│ └─istiofy
│ main.go
│
├─config # config files
│ config.yaml
│
├─dist
│ └─sdk # the typescript SDK will be generated here
│ │ .gitkeep
│
├─docs
│ │ .gitkeep
│ └─swagger-ui # swagger static files
│
├─internal # internal packages here
│ ├─cmd
│ │ root.go
│ │ server.go
│ │
│ ├─dao # database operation
│ │ │ demodb.go
│ │ │ main.go
│ │ │
│ │ └─mysql
│ │ demodb.go
│ │ main.go
│ │
│ ├─gateway
│ │ │ gateway.go
│ │ │ handlers.go
│ │ │
│ │ └─grpc
│ │ grpc.go
│ │
│ ├─model # data models
│ │ base.go
│ │ demodb.go
│ │
│ └─service # service logics
│ demo.go
│ demodb.go
│ main.go
│
├─pkg # public packages
│ ├─build
│ │ build.go
│ │
│ ├─db
│ │ mysql.go
│ │
│ ├─health
│ │ checks.go
│ │ doc.go
│ │ handler.go
│ │ handler_test.go
│ │ timeout.go
│ │ timeout_test.go
│ │ types.go
│ │
│ ├─log
│ │ log.go
│ │
│ └─utils
│ jwt.go
│ md5.go
│
├─schema # database scripts
│ 000.database.sql
│ 001.demodb.sql
│
└─third_party # third party resource folder
└─google
├─api
│ annotations.proto
│ http.proto
│
├─protobuf
│ any.proto
│ descriptor.proto
│ duration.proto
│ empty.proto
│ timestamp.proto
│
└─rpc
code.proto
error_details.proto
status.proto
To upgrade all dependencies at once for a given module, just run the following from the root directory of your module
This upgrades to the latest or minor patch release
go get -u ./...
To also upgrade test dependencies
go get -t -u ./...
make api_dep_install
make api_gen
-
Proto file definition
service Demo { rpc Demo(istiofy.api.general.v1.DemoRequest) returns (istiofy.api.general.v1.DemoResponse) { option (google.api.http) = { get: "/api/v1/demo" }; } }
message Demo { string demo = 1; } message DemoRequest { string demo = 1; } message DemoResponse { Demo demo = 1; }
-
Add RegisterDemoHandler generated by proto in internal/cmd/gateway.go file
func NewGateway(ctx context.Context, conn *grpc.ClientConn, opts []runtime.ServeMuxOption) (http.Handler, error) { mux := runtime.NewServeMux(opts...) for _, f := range []func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error{ // Add follow line, it is generated by protoc v1.RegisterDemoHandler, } { if err := f(ctx, mux, conn); err != nil { return nil, err } } return mux, nil }
-
Create demo.go file in internal/service folder and implement the proto service apis
type DemoService struct { // This is generated by protoc projectv1.UnimplementedDemoServer // Add follow line if you need database operate // dao dao.DemoDao } func NewDemoService() *DemoService { return &DemoService{} } // Replace to follow func if you need database operate // func NewDemoService(dao dao.Interface) *DemoService { // return &DemoService{dao: dao.DemoDao()} // } ... // Service implementation code ...
-
Add new demo func in internal/service/main.go file
func (s *Service) DemoService() DemoService { return *NewDemoService() // Replace to follow line if you need database operate // return *NewDemoService(s.dao) }
-
Add RegisterDemoServer generated by proto in internal/cmd/grpc/grpc.go file
// ... var daoInterface dao.Interface if daoInterface, err = initDao(); err != nil { return err } // ... // Other services register code // ... demoService := service.NewDemoService() // This is generated by protoc v1.RegisterDemoServer(s, demoService) // ... // Other code // ... go func() { defer s.GracefulStop() <-ctx.Done() }() // ...
-
Implemente Demo API in internal/service/demo.go file
func (s *DemoService) Demo(ctx context.Context, req *generalv1.DemoRequest) (*generalv1.DemoResponse, error) { return &generalv1.DemoResponse{ Demo: &generalv1.Demo{ Demo: req.Demo, }, }, nil }
-
Proto file definition
service DemoDb { rpc DemoDb(project.api.general.v1.DemoDbRequest) returns (project.api.general.v1.DemoDbResponse) { option (google.api.http) = { get: "/api/v1/demodb" }; } }
message DemoDb { string demo_db = 1; } message DemoDbRequest { string demo_db = 1; } message DemoDbResponse { DemoDb demo_db = 1; }
-
Add demodb.go in internal/model folder with model definition
type DemoDb struct { Model DemoDb string `json:"demo_db"` }
-
Add demodb.go dao in internal/dao folder and define interface
type DemoDbDao interface { DemoDb(ctx context.Context, demoDb string) (*model.DemoDb, error) }
-
Implement dao interface in demodb.go in internal/dao/mysql with New func
type DemoDbDao struct { Db *gorm.DB } // Implementation interface code func (d DemoDbDao) DemoDb(ctx context.Context, demoDb string) (*model.DemoDb, error) { // This is a demo code ignore it if you have real database logic dDb := &model.DemoDb{ DemoDb: demoDb, } // Add GORM logic here // if err := d.Db.Where("demo_db LIKE ?", "%"+demoDb+"%").Find(&dDb).Error; err != nil { // return nil, err // } return dDb, nil } func NewDemoDbDao(d *gorm.DB) *DemoDbDao { return &DemoDbDao{d} }
-
Add gorm implement func in internal/dao/mysql/main.go file
func (d *Dao) DemoDbDao() dao.DemoDbDao { // There could be added success after interface implement return NewDemoDbDao(d.Db) }
-
Add the new dao in interface of internal/dao/main.go file
type Interface interface { // ... // Other dao interfaces // ... DemoDbDao() DemoDbDao }
-
Add RegisterDemoDbHandler generated by proto in internal/cmd/gateway.go file
func NewGateway(ctx context.Context, conn *grpc.ClientConn, opts []runtime.ServeMuxOption) (http.Handler, error) { mux := runtime.NewServeMux(opts...) for _, f := range []func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error{ // ... // Other code // ... // Add follow line, it is generated by protoc v1.RegisterDemoDbHandler, } { if err := f(ctx, mux, conn); err != nil { return nil, err } } return mux, nil }
-
Create demodb.go file in internal/service folder and implement the proto service apis
type DemoDbService struct { // This is generated by protoc projectv1.UnimplementedDemoDbServer // Remove follow line if you don't need database operate dao dao.DemoDbDao } func NewDemoDbService(dao dao.Interface) *DemoDbService { return &DemoDbService{dao: dao.DemoDbDao()} } // Replace to follow func if you don't need database operate // func NewDemoDbService() *DemoDbService { // return &DemoDbService{} // } ... // Service implementation code ...
-
Add new demo func in internal/service/main.go file
func (s *Service) DemoDbService() DemoDbService { return *NewDemoDbService(s.dao) // Replace to follow line if you don't need database operate // return *NewDemoService() }
-
Add RegisterDemoDbServer generated by proto in internal/cmd/grpc/grpc.go file
// ... var daoInterface dao.Interface if daoInterface, err = initDao(); err != nil { return err } // ... // Other services register code // ... demoDbService := service.NewDemoDbService(daoInterface) // This is generated by protoc v1.RegisterDemoDbServer(s, demoDbService) // ... // Other code // ... go func() { defer s.GracefulStop() <-ctx.Done() }() // ...
-
Implemente Demo API in internal/service/demodb.go file
func (s *DemoDbService) DemoDb(ctx context.Context, req *generalv1.DemoDbRequest) (*generalv1.DemoDbResponse, error) { demoDb, err := s.dao.DemoDb(ctx, req.DemoDb) if err != nil { result := status.Convert(err) if result.Code() == codes.NotFound { return nil, status.Errorf(codes.NotFound, "get err: %s not found", req.DemoDb) } return nil, status.Error(codes.Unknown, err.Error()) } return &generalv1.DemoDbResponse{ DemoDb: &generalv1.DemoDb{ DemoDb: demoDb.DemoDb, }, }, nil }