bluerpc

package module
v0.7.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2024 License: MIT Imports: 19 Imported by: 0

README

Why BlueRPC :

Apps should be as type safe as they can be. Type safety lets you move faster, fix less bugs and be more confident in what you're deploying.

Unfortunately there is no easy lightweight solution to connect a golang backend and a typescript frontend safely. Yet trying to remain in a javascript backend environment by using something has serious drawbacks. Otherwise solutions such as GraphQL or gRPC are rather verbose, requiring you to create intermediate files that describe the structure of your endpoints alongside other things.

BlueRPC is a tRPC inspired backend framework that lets you define a type safe golang server and in turn get a typescript file with everything you need to be able to call that server. It lets you move faster and be more confident that you won't have bugs.

This project is not yet in its stable form. Some small things are still under development. If you would like to help us or ask for features then visit our Github Page.

Quickstart

You first create a new instance of a blueRPC App

package main

import (
	"github.com/blue-rpc/bluerpc"
)

func main() {
	app := bluerpc.New()
    ...
}

Then you need to create a procedure. Procedures can be either Query or Mutation. You attach struct types to these procedures in order to determine what are the acceptable query parameters, input or outputs of them. We will create a query in this case. They can have different query parameters and different outputs.

Start your struct fields with an upper case so that they can be read by blueRPC. Include the paramName tag to say "this field will be named ... in my request query / input or in my output body response"

Return a pointer to a Res struct type at the end of your function. The body must be the type of your output.

{
    ...
	type Query_Params struct {
		Id string `paramName:"id"`
	}

	type Output struct {
		Message string `paramName:"message"`
	}

	HelloWorld := bluerpc.NewQuery[Query_Params, Output](app, 
    func(ctx *bluerpc.Ctx, query Query_Params) (*bluerpc.Res[Output], error) {
		return &bluerpc.Res[Output]{
			Body: Output{
	            Message: fmt.Sprintf("hello world, here is your id: %s", query.id)
			},
		}, nil
	})
    ...
}

Attach your procedure to your App at some endpoint and then run Listen()

//main.go
package main

import (
	"github.com/blue-rpc/bluerpc"
)

func main() {
	app := bluerpc.New()

	type Query_Params struct {
		Id string `paramName:"id"`
	}

	type Output struct {
		Message string `paramName:"message"`
	}

	HelloWorld := bluerpc.NewQuery[Query_Params, Output](app, 
    func(ctx *bluerpc.Ctx, query Query_Params) (*bluerpc.Res[Output], error) {
		return &bluerpc.Res[Output]{
			Body: Output{
				Message: fmt.Sprintf("hello world, here is your id : %s ", query.Id),
			},
		}, nil
	})
	HelloWorld.Attach(app, "/greet")
	app.Listen(":8080")
}

Then run this in your terminal:

go run main.go

Now do a request on http://localhost:8080/greet?id=123 and you should get

{
  "Message": "hello world, here is your id : 123 "
}

You will now get a typescript file with an exported object. Use this object to call all of your fetches from your frontend.

...
export const rpc ={
    greet:{
        query: async (query:{ id?: string,})
                :Promise<{ message?: string,}>
                =>{[...]}as const;
    }
}
Using validation

You can use your favorite validation library to validate your procedure’s inputs and outputs. Just put a validation function that will validate your structs when creating an App.

//We will use go validator in this example

package main
import (
	"testing"
    "github.com/blue-rpc/bluerpc"
	"github.com/go-playground/validator/v10"
)

func main() {
	validate := validator.New(validator.WithRequiredStructEnabled())


    app := bluerpc.New(&bluerpc.Config{
		ValidatorFn:         validate.Struct,
	})
}

Now your attached structs to any procedure will be validated your inputs before reaching your handler and your outputs after

Why not gRPC?

The main issue with gRPC is that it is very verbose. It requires you to create intermediate files that describe your endpoints in a language other than golang.

Despite both having RPC in their name blueRPC does not have many common things with gRPC. BlueRPC tries to only require you to define your routes once and in golang while taking care of everything else.

If runtime speed is of the essence or if you will need to call this server from anything else but a javascript environment then you should definitely use gRPC!

Why not tRPC?

There are 2 main reasons why :

  • tRPC is slow at runtime, especially if you want to add validation libraries like Zod
  • BlueRPC is in GO ecosystem

Yet there are some advantages that tRPC still has, namely being in the JS ecosystem (if you would prefer that for some reason) and being able to go from the type definitions to the endpoint functions immediately in an IDE

Documentation

Index

Constants

View Source
const (
	TextXML               = "text/xml"
	TextHTML              = "text/html"
	TextPlain             = "text/plain"
	ApplicationXML        = "application/xml"
	ApplicationJSON       = "application/json"
	ApplicationJavaScript = "application/javascript"
	ApplicationForm       = "application/x-www-form-urlencoded"
	OctetStream           = "application/octet-stream"
	MultipartForm         = "multipart/form-data"

	TextXMLCharsetUTF8               = "text/xml; charset=utf-8"
	TextHTMLCharsetUTF8              = "text/html; charset=utf-8"
	TextPlainCharsetUTF8             = "text/plain; charset=utf-8"
	ApplicationXMLCharsetUTF8        = "application/xml; charset=utf-8"
	ApplicationJSONCharsetUTF8       = "application/json; charset=utf-8"
	ApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
)

copied from fiber https://docs.gofiber.io/api/constants

Variables

View Source
var DefaultColors = struct {
	Red   string
	Green string
	Reset string
}{
	Red:   "\x1b[31m",
	Green: "\x1b[32m",
	Reset: "\x1b[0m",
}

DefaultColors contains ANSI escape codes for default colors.

Functions

func DefaultErrorMiddleware

func DefaultErrorMiddleware(ctx *Ctx) error

Simple middleware that always returns json

func GetAuth

func GetAuth[authType any](ctx *Ctx) authType

Gets the authorization set data by your authorization function on the context If there is no data OR if the data you've set on your context does not match the generic type you provided then it will trigger a panic

Types

type App

type App struct {
	// contains filtered or unexported fields
}

func New

func New(blueConfig ...*Config) *App

func (*App) Listen

func (a *App) Listen(port string) error

func (*App) PrintRoutes

func (a *App) PrintRoutes()

func (*App) Protected

func (a *App) Protected() *App

it would be very weird for you to make your entire app protected but you can do so by calling this function

func (*App) Router

func (a *App) Router(relativePath string) *Router

func (*App) Shutdown

func (a *App) Shutdown()

func (*App) Static

func (a *App) Static(prefix, root string, config ...*Static)

func (*App) Test

func (a *App) Test(req *http.Request, port ...string) (*http.Response, error)

func (*App) Use

func (a *App) Use(middleware Handler) *App

type AuthHandler

type AuthHandler func(ctx *Ctx) (any, error)

type Authorizer

type Authorizer struct {
	Handler AuthHandler
}

Struct that handles all of the settings related to authorizing for routes and procedures

func NewAuth

func NewAuth(handler AuthHandler) *Authorizer

Creates a new authorizer struct with the defaults set

type Config

type Config struct {

	//Authorizer is the default authorization middleware that your app will use. Whenever you make some procedure protected this function will be used to authorize your user to proceed.
	//You will then be able to call .Auth() on your handler's context and unmarshal whatever you returned first from this function into some variable
	Authorizer *Authorizer

	//  The path where you would like the generated Typescript to be placed.
	// Keep in mind that YOU NEED TO PUT a .ts file at the end of the path
	// Default is ./output.ts
	OutputPath string

	// Boolean that determines if any typescript types will be generated.
	// Default is false. Set this to TRUE in production
	DisableGenerateTS bool

	//The function that will be used to validate your struct fields.
	ValidatorFn validatorFn

	//Disables the fiber logger middleware that is added.
	//False by default. Set this to TRUE in production
	DisableRequestLogging bool

	// by default Bluerpc transforms every error that is sent to the user into a json by the format  ErrorResponse. Turn this to true if you would like fiber to handle what form will the errors have or write your own middleware to convert all of the errors to your liking
	DisableJSONOnlyErrors bool

	//This middleware catches all of the errors of every procedure and handles responding in a proper way
	//By default it is DefaultErrorMiddleware
	ErrorMiddleware Handler

	//the URL of the server. If left empty it will be interpreted as localhost
	ServerURL string

	//disable the printing of that start server message
	DisableInfoPrinting bool

	//determines the default cors origin for every request that comes in
	CORS_Origin string

	//The address that your SSL certificate is located at
	SSLCertPath string

	//The address that your SSL key is located at
	SSLKey string

	// Puts all of the needed Pprof routes in. Read more about pprof here
	// https://pkg.go.dev/net/http/pprof
	EnablePProf bool
}

type Ctx

type Ctx struct {

	// This session field can be used in your middlewares for you to store any data that you would need to pass on to your handlers
	Session any
	// contains filtered or unexported fields
}

func (*Ctx) Attachment

func (c *Ctx) Attachment(filename ...string)

Attachment sets the Content-Disposition header to make the response an attachment. If a filename is provided, it includes it in the header.

func (*Ctx) Get

func (c *Ctx) Get(key string) string

/ This calls the Get method on the http Request to get a value from the header depending on a given key

func (*Ctx) GetCookie added in v0.7.7

func (c *Ctx) GetCookie(key string) (*http.Cookie, error)

returns a specific cookie given the string

func (*Ctx) GetCookies added in v0.7.7

func (c *Ctx) GetCookies() []*http.Cookie

returns an array of all of the cookies on the REQUEST object

func (*Ctx) GetReqHeaders

func (c *Ctx) GetReqHeaders() map[string][]string

GetReqHeaders returns a map of all request headers.

func (*Ctx) GetRespHeaders

func (c *Ctx) GetRespHeaders() map[string][]string

GetRespHeaders returns a COPY map of all response headers.

func (*Ctx) Hostname

func (c *Ctx) Hostname() string

Hostname returns the hostname derived from the Host HTTP header.

func (*Ctx) IP

func (c *Ctx) IP() string

IP returns the remote IP address of the request.

func (c *Ctx) Links(link ...string)

Links sets the Link header in the HTTP response.

func (*Ctx) Method

func (c *Ctx) Method(override ...string) string

Method returns or overrides the HTTP method of the request.

func (*Ctx) Next

func (c *Ctx) Next() error

func (*Ctx) Path

func (c *Ctx) Path(override ...string) string

Path gets or sets the path part of the request URL.

func (*Ctx) Protocol

func (c *Ctx) Protocol() string

Protocol returns the protocol (http or https) used in the request.

func (*Ctx) SendString

func (c *Ctx) SendString(str string) error

func (*Ctx) Set

func (c *Ctx) Set(key, value string)

This calls the Set method on the http response

func (*Ctx) SetCookie added in v0.7.7

func (c *Ctx) SetCookie(cookie *http.Cookie)

type DefaultResError

type DefaultResError struct {
	Message string `json:"message"`
}

type Error

type Error struct {
	Code    int
	Message string
}

func (*Error) Error

func (bluerpcErr *Error) Error() string

type ErrorResponse

type ErrorResponse struct {
	Message string `json:"message"`
}

type Handler

type Handler = func(ctx *Ctx) error
type Header struct {
	Status          int
	Authorization   string         // Credentials for authenticating the client to the server
	CacheControl    string         // Directives for caching mechanisms in both requests and responses
	ContentEncoding string         // The encoding of the body
	ContentType     string         // The MIME type of the body of the request (used with POST and PUT requests)
	Expires         string         // Gives the date/time after which the response is considered stale
	Cookies         []*http.Cookie //Cookies
}

type Map

type Map map[string]interface{}

type Method

type Method string
var (
	QUERY    Method = "query"
	MUTATION Method = "mutation"
	STATIC   Method = "static"
)

type Mutation

type Mutation[query any, input any, output any] func(ctx *Ctx, query query, input input) (*Res[output], error)

First Generic argument is QUERY PARAMETERS. Second is INPUT. Third is OUTPUT.

type Procedure

type Procedure[query any, input any, output any] struct {
	// contains filtered or unexported fields
}

func NewMutation

func NewMutation[query any, input any, output any](app *App, mutation Mutation[query, input, output]) *Procedure[query, input, output]

Creates a new query procedure that can be attached to groups / app root. The generic arguments specify the structure for validating query parameters (the query Params, the body of the request and the resulting handler output). Use any to avoid validation

func NewQuery

func NewQuery[query any, output any](app *App, queryFn Query[query, output]) *Procedure[query, any, output]

Creates a new query procedure that can be attached to groups / app root. The generic arguments specify the structure for validating query parameters (the query Params and the resulting handler output). Use any to avoid validation

func (*Procedure[query, input, output]) AcceptedContentType

func (p *Procedure[query, input, output]) AcceptedContentType(contentTypes ...string)

determines which content type should a request have in order for it to be valid.

func (*Procedure[query, input, output]) Attach

func (proc *Procedure[query, input, output]) Attach(route Route, slug string)

func (*Procedure[query, input, output]) Authorizer

func (p *Procedure[query, input, output]) Authorizer(a *Authorizer) *Procedure[query, input, output]

func (*Procedure[query, input, output]) Protected

func (p *Procedure[query, input, output]) Protected() *Procedure[query, input, output]

Turns the procedure into a protected procedure, meaning your authorization handler will run before this runs

func (*Procedure[query, input, output]) Validator

func (p *Procedure[query, input, output]) Validator(fn validatorFn) *Procedure[query, input, output]

Changes the validator function for this particular procedure

type ProcedureInfo

type ProcedureInfo struct {
	// contains filtered or unexported fields
}

type Query

type Query[query any, output any] func(ctx *Ctx, query query) (*Res[output], error)

First Generic argument is QUERY PARAMS. Second is OUTPUT

type Res

type Res[T any] struct {
	Header Header
	Body   T
}

type Route

type Route interface {
	Router(string) *Router
	// contains filtered or unexported methods
}

type Router

type Router struct {
	// contains filtered or unexported fields
}

func (*Router) Authorizer

func (router *Router) Authorizer(a *Authorizer) *Router

func (*Router) PrintInfo

func (r *Router) PrintInfo()

prints all of the info for this route, including its procedures attached and all of its nested routes

func (*Router) Protected

func (router *Router) Protected() *Router

func (*Router) Router

func (r *Router) Router(slug string) *Router

Creates a new route or returns an existing one if that route was already created

func (*Router) Static

func (r *Router) Static(prefix, root string, config ...*Static)

prefix is the ROUTE PREFIX root is the ROOT folder

func (*Router) Use

func (r *Router) Use(middlewares ...Handler)

func (*Router) Validator

func (r *Router) Validator(fn validatorFn)

changes the validator function for all of the connected procedures unless those procedures directly have validator functions set

type Static

type Static struct {
	// When set to true, enables direct download.
	// Optional. Default value false.
	Download bool `json:"download"`

	// The name of the index file for serving a directory.
	// Optional. Default value "index.html".
	Index string `json:"index"`

	// Expiration duration for inactive file handlers.
	// Use a negative time.Duration to disable it.
	//
	// Optional. Default value 10 * time.Second.
	CacheDuration time.Duration `json:"cache_duration"`

	// The value for the Cache-Control HTTP-header
	// that is set on the file response. MaxAge is defined in seconds.
	//
	// Optional. Default value 0.
	MaxAge int `json:"max_age"`

	// Next defines a function to skip this middleware when returned true.
	//
	// Optional. Default: nil
	Next func(c *Ctx) bool
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL