tanukirpc is a practical, fast-developing, type-safe, and easy-to-use RPC/Router library for Go. This library base on go-chi/chi.
go get -u github.com/mackee/tanukirpcThis is a simple example of how to use tanukirpc.
package main
import (
"fmt"
"net/http"
"github.com/mackee/tanukirpc"
)
type helloRequest struct {
Name string `urlparam:"name"`
}
type helloResponse struct {
Message string `json:"message"`
}
func hello(ctx tanukirpc.Context[struct{}], req helloRequest) (*helloResponse, error) {
return &helloResponse{
Message: fmt.Sprintf("Hello, %s!", req.Name),
}, nil
}
func main() {
r := tanukirpc.NewRouter(struct{}{})
r.Get("/hello/{name}", tanukirpc.NewHandler(hello))
if err := r.ListenAndServe(context.Background(), ":8080"); err != nil {
fmt.Println(err)
}
}tanukiup commandgentypescript commandRegistry injection is unique feature of tanukirpc. You can inject a registry object to the handler function.
Additionally, Registry can be generated for each request. For more details, please refer to _example/simple-registry.
tanukirpc supports the following request bindings by default:
/entity/{id} path): use the urlparam struct tagquery struct tagapplication/json): use the json struct tagapplication/x-www-form-urlencoded): use the form struct tagrawbody struct tag with []byte or io.ReadCloser
If you want to use other bindings, you can implement the tanukirpc.Codec interface and specify it using the tanukirpc.WithCodec option when initializing the router.
tanukirpc.NewRouter(YourRegistry, tanukirpc.WithCodec(yourCodec))tanukirpc automatically validation by go-playground/validator when contains validate struct tag in request struct.
type YourRequest struct {
Name string `form:"name" validate:"required"`
}If you want to use custom validation, you can implement the tanukirpc.Validatable interface in your request struct. tanukirpc will call the Validatable.Validate method after binding the request and before calling the handler function.
tanukirpc has a default error handler. If you want to use custom error handling, you can implement the tanukirpc.ErrorHooker interface and use this with the tanukirpc.WithErrorHooker option when initializing the router.
If you want to return a response with a specific status code, you can use the tanukirpc.WrapErrorWithStatus.
// this handler returns a 404 status code
func notFoundHandler(ctx tanukirpc.Context[struct{}], struct{}) (*struct{}, error) {
return nil, tanukirpc.WrapErrorWithStatus(http.StatusNotFound, errors.New("not found"))
}Also, you can use the tanukirpc.ErrorRedirectTo function. This function returns a response with a 3xx status code and a Location header.
// this handler returns a 301 status code
func redirectHandler(ctx tanukirpc.Context[struct{}], struct{}) (*struct{}, error) {
return nil, tanukirpc.ErrorRedirectTo(http.StatusMovedPermanently, "/new-location")
}You can use tanukirpc with go-chi/chi/middleware or func (http.Handler) http.Handler style middlewares. gorilla/handlers is also included in this.
If you want to use middleware, you can use *Router.Use or *Router.With.
tanukiup commandThe tanukiup command is very useful during development. When you start your server via the tanukiup command, it detects file changes, triggers a build, and restarts the server.
You can use the tanukiup command as follows:
$ go run github.com/mackee/tanukirpc/cmd/tanukiup -dir ./...The -dir option specifies the directory to be watched. By appending ... to the end, it recursively includes all subdirectories in the watch scope. If you want to exclude certain directories, use the -ignore-dir option. You can specify multiple directories by providing comma-separated values or by using the option multiple times. By default, the server will restart when files with the .go extension are updated.
The -addr option allows the tanukiup command to act as a server itself. After building and starting the server application created with tanukirpc, it proxies requests to this process. The application must be started with *tanukirpc.Router.ListenAndServe; otherwise, the -addr option will not function. Only the paths registered with tanukirpc.Router will be proxied to the server application.
Additionally, there is an option called -catchall-target that can be used in conjunction with -addr. This option allows you to proxy requests for paths that are not registered with tanukirpc.Router to another server address. This is particularly useful when working with a frontend development server (e.g., webpack, vite).
Additionally, it detects the go:generate lines for the gentypescript command mentioned later, and automatically runs them before restarting.
A web application server using tanukirpc can generate client-side code based on the type information of each endpoint.
gentypescript generates client-side code specifically for TypeScript. By using the generated client implementation, you can send and receive API requests with type safety for each endpoint.
To generate the client code, first call genclient.AnalyzeTarget with the router as an argument to clearly define the target router.
Next, add the following go:generate line:
//go:generate go run github.com/mackee/tanukirpc/cmd/gentypescript -out ./frontend/src/client.ts ./The -out option specifies the output file name. Additionally, append ./ to specify the package to be analyzed.
When you run go generate ./ in the package containing this file, or when you start the server via the aforementioned tanukiup command, the TypeScript client code will be generated.
For more detailed usage, refer to the _example/todo directory.
tanukirpc supports defer hooks for cleanup. You can register a function to be called after the handler function has been executed.
func (ctx *tanukirpc.Context[struct{}], struct{}) (*struct{}, error) {
ctx.Defer(func() error {
// Close the database connection, release resources, logging, enqueue job etc...
})
return &struct{}{}, nil
}tanukirpc provides convenient utilities for session management. You can use the gorilla/sessions package or other session management libraries.
To get started, create a session store and wrap it using tanukirpc/auth/gorilla.NewStore.
import (
"github.com/gorilla/sessions"
"github.com/mackee/tanukirpc/sessions/gorilla"
tsessions "github.com/mackee/tanukirpc/sessions"
)
func newStore(secrets []byte) (tsessions.Store, error) {
sessionStore := sessions.NewCookieStore(secrets)
store, err := gorilla.NewStore(sessionStore)
if err != nil {
return nil, err
}
return store, nil
}In RegistryFactory, you can create a session using the tanukirpc/sessions.Store.
type RegistryFactory struct {
Store tsessions.Store
}
type Registry struct {
sessionAccessor tsessions.Accessor
}
func (r *RegistryFactory) NewRegistry(w http.ResponseWriter, req *http.Request) (*Registry, error) {
accessor, err := r.Store.GetAccessor(req)
if err != nil {
return nil, fmt.Errorf("failed to get session accessor: %w", err)
}
return &Registry{
sessionAccessor: accessor,
}, nil
}
func (r *Registry) Session() tsessions.Accessor {
return r.sessionAccessor
}The Registry type implements the tanukirpc/sessions.RegistryWithAccessor interface.
tanukirpc supports the OpenID Connect authentication flow. You can use the tanukirpc/auth/oidc.NewHandlers function to create handlers for this flow, which includes a set of handlers to facilitate user authentication.
tanukirpc/auth/oidc.Handlers requires a Registry that implements the tanukirpc/sessions.RegistryWithAccessor interface. For more details, refer to the Session Management section.
oidcAuth := oidc.NewHandlers(
oauth2Config, // *golang.org/x/oauth2.Config
provider, // *github.com/coreos/go-oidc/v3/oidc.Provider
)
router.Route("/auth", func(router *tanukirpc.Router[*Registry]) {
router.Get("/redirect", tanukirpc.NewHandler(oidcAuth.Redirect))
router.Get("/callback", tanukirpc.NewHandler(oidcAuth.Callback))
router.Get("/logout", tanukirpc.NewHandler(oidcAuth.Logout))
})Copyright (c) 2024- mackee
Licensed under MIT License.