Skip to content

derekdowling/jsh-api

Repository files navigation

JSH-API

THIS REPO IS DEPRECATED!

The official version of jsh-api now lives here as a submodule of JSH.


GoDoc Build Status Go Report Card

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.

Setup

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)

Feature Overview

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.

Simple Default CRUD Implementation

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)

Relationships

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)

Custom Actions

  • GET /resources/:id/
resourceStorage := &ResourceStorage{}
resource := jshapi.NewResource("resources", resourceStorage)
resource.Action("reset", resetAction)

Other Features

  • Default Request, Response, and 5XX Auto-Logging

Working With Storage Interfaces

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
}