NOTICE: Under development. Seeking early user feedback.
GoSpeak is a simple RPC framework, a lightweight alternative to gRPC and Twirp, where Go code is your protobuf.
//go:generate github.com/golang-cz/gospeak/cmd/gospeak ./
package proto
//go:webrpc golang -server -pkg=server -out=./server/server.gen.go
//go:webrpc golang -client -pkg=client -out=./client/example.gen.go
type ExampleAPI interface {
Ping(context.Context, *Ping) (*Pong, error)
}
Usage:
$ go get github.com/golang-cz/gospeak/cmd/gospeak@latest
$ go generate
ExampleAPI => ./server/server.gen.go ✓
ExampleAPI => ./client/example.gen.go ✓
GoSpeak uses webrpc-gen tool to generate REST API client & server code using Go templates. The API routes and JSON payload are defined per webrpc specs and can be exported to OpenAPI 3.x (Swagger) documentation.
Server | Client | |
---|---|---|
Go 1.20+ | <=> | Go 1.17+ |
Go 1.20+ | <=> | TypeScript |
Go 1.20+ | <=> | JavaScript (ES6) |
Go 1.20+ | <=> | OpenAPI 3+ (Swagger documentation) |
Go 1.20+ | <=> | Any OpenAPI client code generator |
- 1. Define service API
- 2. Add target language directives
- 3. Generate code
- 4. Mount the API server
- 5. Implement the server business logic
- 6. Use the generated client
- 7. Test your API
package proto
import "context"
type PetStore interface {
GetPet(ctx context.Context, ID int64) (pet *Pet, err error)
ListPets(ctx context.Context) (pets []*Pet, err error)
CreatePet(ctx context.Context, new *Pet) (pet *Pet, err error)
UpdatePet(ctx context.Context, ID int64, update *Pet) (pet *Pet, err error)
DeletePet(ctx context.Context, ID int64) error
}
type Pet struct {
ID int64
Name string
Available bool
PhotoURLs []string
Tags []Tag
}
type Tag struct {
ID int64
Name string
}
Generate Go server and Go client code with go:webrpc
directives:
+//go:webrpc golang -server -pkg=server -out=./server/server.gen.go
+//go:webrpc golang -client -pkg=client -out=./client/example.gen.go
type PetStore interface {
Generate TypeScript client and OpenAPI 3.x (Swagger) documentation:
//go:webrpc golang -server -pkg=server -out=./server/server.gen.go
//go:webrpc golang -client -pkg=client -out=./client/example.gen.go
+//go:webrpc typescript -client -out=./client/exampleClient.gen.ts
+//go:webrpc openapi -out=./docs/exampleApi.gen.yaml -title=PetStoreAPI
type PetStore interface {
Install gospeak and generate the webrpc code.
$ gospeak ./proto/api.go
PetStore => ./server/server.gen.go ✓
PetStore => ./client/client.gen.go ✓
PetStore => ./docs/videoApi.gen.yaml ✓
PetStore => ./client/videoDashboardClient.gen.ts ✓
NOTE: Alternatively, you can go get github.com/golang-cz/gospeak
as your dependency and run go generate
against //go:generate github.com/golang-cz/gospeak/cmd/gospeak .
directive.
// cmd/petstore/main.go
package main
import "./server"
func main() {
api := &server.Server{} // implements PetStore interface{}
handler := server.NewPetStoreServer(api)
http.ListenAndServe(":8080", handler)
}
The generated server code already
- handles incoming REST API requests
- unmarshals JSON request into method argument(s)
- calls your RPC method implementation, ie. `server.GetPet(ctx, 1)``
- marshals return argument(s) into a JSON response
What's left is the business logic. Implement the interface methods:
// rpc/server.go
package rpc
type Server struct {
/* DB connection, config etc. */
}
// rpc/user.go
package rpc
func (s *Server) GetUser(ctx context.Context, uid string) (user *User, err error) {
user, err := s.DB.GetUser(ctx, uid)
if err != nil {
if errors.Is(err, io.EOF) {
return nil, Errorf(ErrNotFound, "failed to find user(%v)", uid)
}
return nil, WrapError(ErrInternal, err, "failed to fetch user(%v)", uid)
}
return user, nil
}
See source code
package main
import "./client"
func main() {
api := client.NewPetStoreClient("http://localhost:8080", http.DefaultClient)
pets, err := api.ListPets(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(pets)
}
package test
import (
"testing"
"./client"
)
func TestAPI(t *testing.T){
api := client.NewPetStoreClient("http://localhost:8080", http.DefaultClient)
pets, err := api.ListPets(ctx)
if err != nil {
t.Fatal(err)
}
t.Log(pets)
}
..and let us know what you think in discussions.