github.com/abemedia/go-don@v0.2.2-0.20240329015135-be88e32bb73b/README.md (about)

     1  # Don - Go API Framework
     2  
     3  [![GoDoc](https://pkg.go.dev/badge/github.com/abemedia/go-don)](https://pkg.go.dev/github.com/abemedia/go-don)
     4  [![Codecov](https://codecov.io/gh/abemedia/go-don/branch/master/graph/badge.svg)](https://codecov.io/gh/abemedia/go-don)
     5  [![Go Report Card](https://goreportcard.com/badge/github.com/abemedia/go-don)](https://goreportcard.com/report/github.com/abemedia/go-don)
     6  
     7  Don is a fast & simple API framework written in Go. It features a super-simple API and thanks to
     8  [fasthttp](https://github.com/valyala/fasthttp) and a custom version of
     9  [httprouter](https://github.com/abemedia/httprouter) it's blazing fast and has a low memory
    10  footprint.
    11  
    12  As Don uses Go generics it requires Go 1.18 to work.  
    13  Minor version updates should be considered breaking changes.
    14  
    15  ## Contents
    16  
    17  - [Overview](#don---go-api-framework)
    18    - [Basic Example](#basic-example)
    19    - [Configuration](#configuration)
    20    - [Support multiple formats](#support-multiple-formats)
    21      - [Currently supported formats](#currently-supported-formats)
    22      - [Adding custom encoding](#adding-custom-encoding)
    23    - [Request parsing](#request-parsing)
    24    - [Headers & response codes](#headers--response-codes)
    25    - [Sub-routers](#sub-routers)
    26    - [Middleware](#middleware)
    27    - [Benchmarks](#benchmarks)
    28  
    29  ## Basic Example
    30  
    31  ```go
    32  package main
    33  
    34  import (
    35    "context"
    36    "errors"
    37    "fmt"
    38    "net/http"
    39  
    40    "github.com/abemedia/go-don"
    41    _ "github.com/abemedia/go-don/encoding/json" // Enable JSON parsing & rendering.
    42    _ "github.com/abemedia/go-don/encoding/yaml" // Enable YAML parsing & rendering.
    43  )
    44  
    45  type GreetRequest struct {
    46    Name string `path:"name"`         // Get name from the URL path.
    47    Age  int    `header:"X-User-Age"` // Get age from HTTP header.
    48  }
    49  
    50  type GreetResponse struct {
    51    // Remember to add all the tags for the renderers you enable.
    52    Greeting string `json:"data" yaml:"data"`
    53  }
    54  
    55  func Greet(ctx context.Context, req GreetRequest) (*GreetResponse, error) {
    56    if req.Name == "" {
    57      return nil, don.Error(errors.New("missing name"), http.StatusBadRequest)
    58    }
    59  
    60    res := &GreetResponse{
    61      Greeting: fmt.Sprintf("Hello %s, you're %d years old.", req.Name, req.Age),
    62    }
    63  
    64    return res, nil
    65  }
    66  
    67  func Pong(context.Context, any) (string, error) {
    68    return "pong", nil
    69  }
    70  
    71  func main() {
    72    r := don.New(nil)
    73    r.Get("/ping", don.H(Pong)) // Handlers are wrapped with `don.H`.
    74    r.Post("/greet/:name", don.H(Greet))
    75    r.ListenAndServe(":8080")
    76  }
    77  ```
    78  
    79  ## Configuration
    80  
    81  Don is configured by passing in the `Config` struct to `don.New`.
    82  
    83  ```go
    84  r := don.New(&don.Config{
    85    DefaultEncoding: "application/json",
    86    DisableNoContent: false,
    87  })
    88  ```
    89  
    90  ### DefaultEncoding
    91  
    92  Set this to the format you'd like to use if no `Content-Type` or `Accept` headers are in the
    93  request.
    94  
    95  ### DisableNoContent
    96  
    97  If you return `nil` from your handler, Don will respond with an empty body and a `204 No Content`
    98  status code. Set this to `true` to disable that behaviour.
    99  
   100  ## Support multiple formats
   101  
   102  Support multiple request & response formats without writing extra parsing or rendering code. The API
   103  uses the `Content-Type` and `Accept` headers to determine what input and output encoding to use.
   104  
   105  You can mix multiple formats, for example if the `Content-Type` header is set to `application/json`,
   106  however the `Accept` header is set to `application/x-yaml`, then the request will be parsed as JSON,
   107  and the response will be YAML encoded.
   108  
   109  If no `Content-Type` or `Accept` header is passed the default will be used.
   110  
   111  Formats need to be explicitly imported e.g.
   112  
   113  ```go
   114  import _ "github.com/abemedia/go-don/encoding/yaml"
   115  ```
   116  
   117  ### Currently supported formats
   118  
   119  #### JSON
   120  
   121  MIME: `application/json`
   122  
   123  Parses JSON requests & encodes responses as JSON. Use the `json` tag in your request & response
   124  structs.
   125  
   126  #### XML
   127  
   128  MIME: `application/xml`, `text/xml`
   129  
   130  Parses XML requests & encodes responses as XML. Use the `xml` tag in your request & response
   131  structs.
   132  
   133  #### YAML
   134  
   135  MIME: `application/yaml`, `text/yaml`, `application/x-yaml`, `text/x-yaml`, `text/vnd.yaml`
   136  
   137  Parses YAML requests & encodes responses as YAML. Use the `yaml` tag in your request & response
   138  structs.
   139  
   140  #### Form (input only)
   141  
   142  MIME: `application/x-www-form-urlencoded`, `multipart/form-data`
   143  
   144  Parses form data requests. Use the `form` tag in your request struct.
   145  
   146  #### Text
   147  
   148  MIME: `text/plain`
   149  
   150  Parses non-struct requests and encodes non-struct responses e.g. `string`, `int`, `bool` etc.
   151  
   152  ```go
   153  func MyHandler(ctx context.Context, req int64) (string, error) {
   154    // ...
   155  }
   156  ```
   157  
   158  If the request is a struct and the `Content-Type` header is set to `text/plain` it returns a
   159  `415 Unsupported Media Type` error.
   160  
   161  #### MessagePack
   162  
   163  MIME: `application/msgpack`, `application/x-msgpack`, `application/vnd.msgpack`
   164  
   165  Parses MessagePack requests & encodes responses as MessagePack. Use the `msgpack` tag in your
   166  request & response structs.
   167  
   168  #### TOML
   169  
   170  MIME: `application/toml`
   171  
   172  Parses TOML requests & encodes responses as TOML. Use the `toml` tag in your request & response
   173  structs.
   174  
   175  #### Protocol Buffers
   176  
   177  MIME: `application/protobuf`, `application/x-protobuf`
   178  
   179  Parses protobuf requests & encodes responses as protobuf. Use pointer types generated with `protoc`
   180  as your request & response structs.
   181  
   182  ### Adding custom encoding
   183  
   184  Adding your own is easy. See [encoding/json/json.go](./encoding/json/json.go).
   185  
   186  ## Request parsing
   187  
   188  Automatically unmarshals values from headers, URL query, URL path & request body into your request
   189  struct.
   190  
   191  ```go
   192  type MyRequest struct {
   193    // Get from the URL path.
   194    ID int64 `path:"id"`
   195  
   196    // Get from the URL query.
   197    Filter string `query:"filter"`
   198  
   199    // Get from the JSON, YAML, XML or form body.
   200    Content float64 `form:"bar" json:"bar" yaml:"bar" xml:"bar"`
   201  
   202    // Get from the HTTP header.
   203    Lang string `header:"Accept-Language"`
   204  }
   205  ```
   206  
   207  Please note that using a pointer as the request type negatively affects performance.
   208  
   209  ## Headers & response codes
   210  
   211  Implement the `StatusCoder` and `Headerer` interfaces to customise headers and response codes.
   212  
   213  ```go
   214  type MyResponse struct {
   215    Foo  string `json:"foo"`
   216  }
   217  
   218  // Set a custom HTTP response code.
   219  func (nr *MyResponse) StatusCode() int {
   220    return 201
   221  }
   222  
   223  // Add custom headers to the response.
   224  func (nr *MyResponse) Header() http.Header {
   225    header := http.Header{}
   226    header.Set("foo", "bar")
   227    return header
   228  }
   229  ```
   230  
   231  ## Sub-routers
   232  
   233  You can create sub-routers using the `Group` function:
   234  
   235  ```go
   236  r := don.New(nil)
   237  sub := r.Group("/api")
   238  sub.Get("/hello", don.H(Hello))
   239  ```
   240  
   241  ## Middleware
   242  
   243  Don uses the standard fasthttp middleware format of
   244  `func(fasthttp.RequestHandler) fasthttp.RequestHandler`.
   245  
   246  For example:
   247  
   248  ```go
   249  func loggingMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
   250    return func(ctx *fasthttp.RequestCtx) {
   251      log.Println(string(ctx.RequestURI()))
   252      next(ctx)
   253    }
   254  }
   255  ```
   256  
   257  It is registered on a router using `Use` e.g.
   258  
   259  ```go
   260  r := don.New(nil)
   261  r.Post("/", don.H(handler))
   262  r.Use(loggingMiddleware)
   263  ```
   264  
   265  Middleware registered on a group only applies to routes in that group and child groups.
   266  
   267  ```go
   268  r := don.New(nil)
   269  r.Get("/login", don.H(loginHandler))
   270  r.Use(loggingMiddleware) // applied to all routes
   271  
   272  api := r.Group("/api")
   273  api.Get("/hello", don.H(helloHandler))
   274  api.Use(authMiddleware) // applied to routes `/api/hello` and `/api/v2/bye`
   275  
   276  
   277  v2 := api.Group("/v2")
   278  v2.Get("/bye", don.H(byeHandler))
   279  v2.Use(corsMiddleware) // only applied to `/api/v2/bye`
   280  
   281  ```
   282  
   283  To pass values from the middleware to the handler extend the context e.g.
   284  
   285  ```go
   286  func myMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
   287    return func(ctx *fasthttp.RequestCtx) {
   288      ctx.SetUserValue(ContextUserKey, "my_user")
   289      next(ctx)
   290    }
   291  }
   292  ```
   293  
   294  This can now be accessed in the handler:
   295  
   296  ```go
   297  user := ctx.Value(ContextUserKey).(string)
   298  ```
   299  
   300  ## Benchmarks
   301  
   302  To give you a rough idea of Don's performance, here is a comparison with Gin.
   303  
   304  ### Request Parsing
   305  
   306  Don has extremely fast & efficient binding of request data.
   307  
   308  | Benchmark name           |     (1) |         (2) |       (3) |          (4) |
   309  | ------------------------ | ------: | ----------: | --------: | -----------: |
   310  | BenchmarkDon_BindRequest | 2947474 | 390.3 ns/op |   72 B/op |  2 allocs/op |
   311  | BenchmarkGin_BindRequest |  265609 |  4377 ns/op | 1193 B/op | 21 allocs/op |
   312  
   313  Source: [benchmarks/binding_test.go](./benchmarks/binding_test.go)
   314  
   315  ### Serving HTTP Requests
   316  
   317  Keep in mind that the majority of time here is actually the HTTP roundtrip.
   318  
   319  | Benchmark name    |   (1) |         (2) |       (3) |          (4) |
   320  | ----------------- | ----: | ----------: | --------: | -----------: |
   321  | BenchmarkDon_HTTP | 45500 | 25384 ns/op |   32 B/op |  3 allocs/op |
   322  | BenchmarkGin_HTTP | 22995 | 49865 ns/op | 2313 B/op | 21 allocs/op |
   323  
   324  Source: [benchmarks/http_test.go](./benchmarks/http_test.go)