Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jarrettv/go-japi
Japi is a fast & simple HTTP API library that automatically marshals JSON payloads and uses RFC7807 for problem details
https://github.com/jarrettv/go-japi
go golang json json-api middleware router
Last synced: about 2 months ago
JSON representation
Japi is a fast & simple HTTP API library that automatically marshals JSON payloads and uses RFC7807 for problem details
- Host: GitHub
- URL: https://github.com/jarrettv/go-japi
- Owner: jarrettv
- License: mit
- Created: 2022-04-18T06:24:36.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-05-31T03:36:15.000Z (over 2 years ago)
- Last Synced: 2024-11-02T07:23:40.190Z (about 2 months ago)
- Topics: go, golang, json, json-api, middleware, router
- Language: Go
- Homepage:
- Size: 3.6 MB
- Stars: 19
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# japi is a JSON HTTP API go library
Japi is a fast & simple HTTP API library that will automatically marshal JSON payloads to/from
your request and response structs. It follows [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807)
standard for returning useful problem details.This library focuses on happy path to minimize code and dependencies. For more complex use cases,
we recommend sticking to a larger web framework. However, this library supports the standard
net/http ecosystem.This library requires Go 1.18 to work as it utilizes generics.
This library was forked from https://github.com/AbeMedia/go-don
## Contents
- [Basic Example](#basic-example)
- [Configuration](#configuration)
- [Request parsing](#request-parsing)
- [Customize response](#customize-response)
- [Problem details](#problem-details)
- [Sub-routers](#sub-routers)
- [Middleware](#middleware)## Basic Example
```go
package mainimport (
"context"
"errors"
"fmt"
"net/http""github.com/jarrettv/go-japi"
)type GreetRequest struct {
Name string `path:"name"` // Get name from the URL path.
Age int `header:"X-User-Age"` // Get age from HTTP header.
}type GreetResponse struct {
// Remember to add tags for automatic marshalling
Greeting string `json:"data"`
}func Greet(ctx context.Context, req GreetRequest) (*GreetResponse, error) {
if req.Name == "" {
return nil, problem.Validation(map[string]string{
"name": "required",
})
}
res := &GreetResponse{
Greeting: fmt.Sprintf("Hello %s, you're %d years old.", req.Name, req.Age),
}return res, nil
}func Pong(context.Context, japi.Empty) (string, error) {
return "pong", nil
}func main() {
r := japi.New(nil)
r.Get("/ping", japi.H(Pong)) // Handlers are wrapped with `japi.H`.
r.Post("/greet/:name", japi.H(Greet))
r.ListenAndServe(":8080")
}
```## Configuration
Japi is configured by passing in the `Config` struct to `japi.New`. We recommend you setup `ProblemConfig` at a minimum.
```go
r := japi.New(&japi.Config{
ProblemConfig: problem.ProblemConfig{
ProblemTypeUrlFormat: "https://example.com/errors/%s",
ProblemInstanceFunc: func(ctx context.Context) string {
return fmt.Sprintf("https://example.com/trace/%d", time.Now().UnixMilli())
},
},
})
```
### RouteLogFuncA function to easily log the route name and route variables.
### ProblemLogFunc
A function to easily log when problems occur.
### ProblemConfig.ProblemTypeUrlFormat
The format for the problem details type URI. See [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807)
### ProblemConfig.ProblemInstanceFunc
A function for generating a unique trace URI. Defaults to a timestamp. See [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807)
## Request parsing
Automatically unmarshals values from headers, URL query, URL path & request body into your request
struct.```go
type MyRequest struct {
// Get from the URL path.
ID int64 `path:"id"`// Get from the URL query.
Filter string `query:"filter"`// Get from the JSON or form body.
Content float64 `form:"bar" json:"bar"`// Get from the HTTP header.
Lang string `header:"Accept-Language"`
}
```Please note that using a pointer as the request type negatively affects performance.
## Customize Response
Implement the `StatusCoder` and `Headerer` interfaces to customise headers and response codes.
```go
type MyResponse struct {
Foo string `json:"foo"`
}// Set a custom HTTP response code.
func (nr *MyResponse) StatusCode() int {
return 201
}// Add custom headers to the response.
func (nr *MyResponse) Header() http.Header {
header := http.Header{}
header.Set("foo", "bar")
return header
}
```## Problems
Return a `problem.Problem` error when something goes wrong. For example:
```go
return nil, problem.Unexpected(err) // 500
// or
return nil, problem.NotFound() // 404
// or
return nil, problem.NotPermitted(username) // 403
// or
return nil, problem.Validation(map[string]string{ // 400
"name": "required",
})
// or
return nil, problem.RuleViolantion("item is on backorder") // 400
// or
return nil, problem.NotCurrent() // 407
```## Sub-routers
You can create sub-routers using the `Group` function:
```go
r := japi.New(nil)
sub := r.Group("/api")
sub.Get("/hello")
```## Middleware
Japi uses the standard http middleware format of
`func(http.RequestHandler) http.RequestHandler`.For example:
```go
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL)
next(ctx)
})
}
```It is registered on a router using `Use` e.g.
```go
r := japi.New(nil)
r.Post("/", japi.H(handler))
r.Use(loggingMiddleware)
```Middleware registered on a group only applies to routes in that group and child groups.
```go
r := japi.New(nil)
r.Get("/login", japi.H(loginHandler))
r.Use(loggingMiddleware) // applied to all routesapi := r.Group("/api")
api.Get("/hello", japi.H(helloHandler))
api.Use(authMiddleware) // applied to routes `/api/hello` and `/api/v2/bye`v2 := api.Group("/v2")
v2.Get("/bye", japi.H(byeHandler))
v2.Use(corsMiddleware) // only applied to `/api/v2/bye````
To pass values from the middleware to the handler extend the context e.g.
```go
func myMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ContextUserKey, "my_user")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
```This can now be accessed in the handler:
```go
user := ctx.Value(ContextUserKey).(string)
```