A JSON API specification micro-service builder created on top of jsh, Goji, and context to handle the nitty gritty but predictable (un)wrapping, validating, preparing, and logging necessary for any JSON API written in Go. The rest (storage, and business logic) is up to you.
The easiest way to get started is like so:
import github.com/derekdowling/jsh-api
// implement jshapi/store.CRUD interface and add resource specific middleware via Goji
userStorage := &UserStorage{}
resource := jshapi.NewCRUDResource("user", userStorage)
resource.UseC(yourUserMiddleware)
// setup a logger, your shiny new API, and give it a resource
logger := log.New(os.Stderr, "<yourapi>: ", log.LstdFlags)
api := jshapi.Default("<prefix>", true, logger)
api.Add(resource)
// launch your api
http.ListenAndServe("localhost:8000", api)
For a completely custom setup:
import github.com/derekdowling/jsh-api
// manually setup your API
api := jshapi.New("<prefix>")
// add a custom send handler
jshapi.SendHandler = func(c context.Context, w http.ResponseWriter, r *http.Request, sendable jsh.Sendable) {
// do some custom logging, or manipulation
jsh.Send(w, r, sendable)
}
// add top level Goji Middleware
api.UseC(yourTopLevelAPIMiddleware)
http.ListenAndServe("localhost:8000", api)
There are a few things you should know about JSHAPI. First, this project is maintained with emphasis on these two guiding principles:
- reduce JSONAPI boilerplate in your code as much as possible
- keep separation of concerns in mind, let developers decide and customize as much as possible
The other major point is that this project uses a small set of storage interfaces that make handling API actions endpoint simple and consistent. In each of the following examples, these storage interfaces are utilized. For more information about how these work, see the Storage Example.
Quickly build resource APIs for:
- POST /resources
- GET /resources
- GET /resources/:id
- DELETE /resources/:id
- PATCH /resources/:id
resourceStorage := &ResourceStorage{}
resource := jshapi.NewCRUDResource("resources", resourceStorage)
Routing for relationships too:
- GET /resources/:id/relationships/otherResource[s]
- GET /resources/:id/otherResource[s]
resourceStorage := &ResourceStorage{}
resource := jshapi.NewResource("resources", resourceStorage)
resource.ToOne("foo", fooToOneStorage)
resource.ToMany("bar", barToManyStorage)
- GET /resources/:id/
resourceStorage := &ResourceStorage{}
resource := jshapi.NewResource("resources", resourceStorage)
resource.Action("reset", resetAction)
- Default Request, Response, and 5XX Auto-Logging
Below is a basic example of how one might implement parts of a CRUD Storage interface for a basic user resource using jsh for Save and Update. This should give you a pretty good idea of how easy it is to implement the Storage driver with jsh.
type User struct {
ID string
Name string `json:"name"`
}
func Save(ctx context.Context, object *jsh.Object) (*jsh.Object, jsh.ErrorType) {
user := &User{}
err := object.Unmarshal("user", user)
if err != nil {
return err
}
// generate your id, however you choose
user.ID = "1234"
err := object.Marshal(user)
if err != nil {
return nil, err
}
// do save logic
return object, nil
}
func Update(ctx context.Context, object *jsh.Object) (*jsh.Object, jsh.ErrorType) {
user := &User{}
err := object.Unmarshal("user", user)
if err != nil {
return err
}
user.Name = "NewName"
err := object.Marshal(user)
if err != nil {
return nil, err
}
// perform patch
return object, nil
}