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)