All Projects → stephenmuss → Argo

stephenmuss / Argo

Licence: mit
JSON API (jsonapi.org) implementation for Clojure

Programming Languages

clojure
4091 projects

Labels

Projects that are alternatives of or similar to Argo

jsonapi-serializer-formats
💎 Gem to enrich jsonapi-serializer with multiple formats
Stars: ✭ 20 (-41.18%)
Mutual labels:  jsonapi
laravel5-jsonapi-dingo
Laravel5 JSONAPI and Dingo together to build APIs fast
Stars: ✭ 29 (-14.71%)
Mutual labels:  jsonapi
Laravel5 Jsonapi
Laravel 5 JSON API Transformer Package
Stars: ✭ 313 (+820.59%)
Mutual labels:  jsonapi
ember-custom-actions
Custom API actions for Ember applications
Stars: ✭ 73 (+114.71%)
Mutual labels:  jsonapi
json-api-doc
JSON API parser returning a simple Python dictionary
Stars: ✭ 24 (-29.41%)
Mutual labels:  jsonapi
Vox
Swift JSON:API client framework
Stars: ✭ 47 (+38.24%)
Mutual labels:  jsonapi
jsonapi-client
A convenient module to consume a jsonapi service
Stars: ✭ 38 (+11.76%)
Mutual labels:  jsonapi
Api2go
JSONAPI.org Implementation for Go
Stars: ✭ 632 (+1758.82%)
Mutual labels:  jsonapi
jsonapi-scala
jsonapi.org library for scala
Stars: ✭ 22 (-35.29%)
Mutual labels:  jsonapi
Reservoir
A back end for your front end: a content repository. Powered by Drupal 8, JSON API and OAuth2.
Stars: ✭ 262 (+670.59%)
Mutual labels:  jsonapi
json-api-serializer
Node.js/browser framework agnostic JSON API (http://jsonapi.org/) serializer.
Stars: ✭ 141 (+314.71%)
Mutual labels:  jsonapi
kurier
TypeScript framework to create JSON:API compliant APIs
Stars: ✭ 30 (-11.76%)
Mutual labels:  jsonapi
mu-cl-resources
High-level abstractions for generating generic jsonapi compliant resources configured in Common Lisp.
Stars: ✭ 14 (-58.82%)
Mutual labels:  jsonapi
laravel5-hal-json
Laravel 5 HAL+JSON API Transformer Package
Stars: ✭ 15 (-55.88%)
Mutual labels:  jsonapi
Redux Json Api
Redux actions, action creators and reducers to make life with JSON APIs a breeze.
Stars: ✭ 374 (+1000%)
Mutual labels:  jsonapi
json-server
Create a dummy REST API from a json file with zero coding in seconds
Stars: ✭ 34 (+0%)
Mutual labels:  jsonapi
jsonapi-deserializable
Conveniently deserialize JSON API payloads into custom hashes.
Stars: ✭ 23 (-32.35%)
Mutual labels:  jsonapi
Timed Backend
Django API for the Timed application
Stars: ✭ 15 (-55.88%)
Mutual labels:  jsonapi
Flask Rest Jsonapi
Flask extension to build REST APIs around JSONAPI 1.0 specification.
Stars: ✭ 566 (+1564.71%)
Mutual labels:  jsonapi
jsonapi-swagger
Create a JSONAPI Swagger.
Stars: ✭ 49 (+44.12%)
Mutual labels:  jsonapi

argo

Clojarsroject

Argo is a Clojure library to help build APIs in Clojure that conform to the JSON API 1.0 specification.

The library is still in its early stages and does not yet fully adhere to the specification. However, most of the main areas of the spec are already covered.

Resources

Basics

To create a new resource use argo's defresource macro.

(require '[argo.core :refer [defresource]])

(defresource heroes {})

The above is the minimum required to define a new resource. Although in this case only OPTIONS requests would be allowed at the endpoint /heroes. In order to allow the usual CRUD operations the following should be implemented:

  • :find: This will allow GET requests at /heroes in order to return a response of many heroes.
  • :get: Implementing this allows for a GET request to be made at /heroes/:id in order to be able to retrieve a single hero.
  • :create: Allows for POST requests to be made at /heroes in order to add or create new heroes.
  • :update: Implementing this will allow for PATCH requests to be sent to /heroes/:id in order to update an existing hero.
  • :delete: DELETE requests will be allowed at /heroes/:id in order to remove or delete an existing hero.

Each operation expects its implementation to be a function with a ring request object as its sole argument.

Argo expects as the result of these functions a map with the following keys:

  • :data: The data which will be returned in the API response. This should be a map for a single resource (as implemented with :get) or a vector/sequence for :find.
  • :errors: A map of keywords and string values. Will be converted to the JSON API error format. Works well with Prismatic schema error types.
  • :status: Use to override the status of responses. Defaults to 400 for error responses and 200 for valid responses.
  • :exclude-source: Use this to exclude the source object as per the JSON API spec in error responses.
  • :count: argo provides automatic generation of pagination links if using pagination for :find. Use :count to let argo know how many total objects exist when implementing pagination.

In most circumstances it will probably only be necessary to include either :data or :errors.

To expand on the previous example:

(require '[argo.core :refer [defresource]])

(defresource heroes {
  :find (fn [req]
          {:data (heroes/get-all)})

  :get (fn [req]
         {:data (heroes/get-one (-> req :params :id))})  ; will raise 404 if :data is nil

  :create (fn [req]
            (if-let [errors (heroes/validate-new (:body req))]
              {:errors errors}
              {:data (heroes/new (:body req))}))

  :update (fn [req]
            (if-let [errors (heroes/validate-update (:body req))]
              {:errors errors}
              {:data (heroes/update (-> req :params :id) (:body req))}))

  :delete (fn [req]
            (when-let [errors (heroes/delete (-> req :params :id))]
              {:errors errors}))})

Resource Creation

Argo provides a helper for the creation and validation of new objects. This is designed to be used in unison with Prismatic's schema libaray.

Supposing we want to validate new Heroes we would first create a schema for Hero.

(require '[schema.core :as s]
         '[argo.core :refer [CreateRequest]])

(s/defschema Hero
  {:name (s/named s/Str "hero name must be a string")
   :birthplace (s/named s/Str "hero birthplace must be a string")})

We can then use argo's CreateRequest to generate a schema which can then be used to ensure that POST requests to create new heroes are in the correct jsonapi format.

(def NewHero (CreateRequest :heroes Hero))

Now in the :create request handler the NewHero schema can be used to validate the request body.

(defresource heroes
  ; ...
  :create (fn [req]
    (if-let [errors (s/check NewHero (:body req))]
      {:errors errors}
      ;; otherwise create new hero
      {:data (create-hero (:body req))})))

Relationships

In order for argo to be able to generate relationship links it is necessary to implement relationships with the key :rels.

:rels is a map of maps. They key used in the map will be that which is used for url generation. Relationships may have the following keys.

  • :type: (required) indicates the relationship type. Use a string or keyword for to-one relationships and a vector of a single keyword or string for to-many relationships.
  • :foreign-key: For to-one relationships this should match the key in the parent resource in order for the key to be removed from the JSON API attributes object.
  • :get: Implement in order to be able to allow get requests to the relationships endpoint.
  • :create: Implement this in order to create a new relationship.
  • :update: Updates an existing relationship.
  • :delete: Remove an existing relationship.

Generally, only errors should be returned for relationship requests. The expected response will usually be a 204 No Content response.

Extending the heroes example let's create an :achievements relationship to our resource. This will create endpoints at /heroes/:id/achievements.

  :rels {:achievements {:type [:achievements]

                        :get (fn [req]
                               {:data (heroes/get-achievements (-> req :params :id))})

                        :create (fn [req]
                                  (if-let [errors (heroes/validate-achievement (:body req))]
                                  {:errors errors}
                                  (heroes/add-achievement (-> req :params :id) (:body req))))

                        :update (fn [req]
                                  (if-let [errors (heroes/validate-achievement-update (:body req))
                                    {:errors errors}]
                                    (heroes/update-achievement (-> req :params :id) (:body req))))

                        :delete (fn [req]
                                  (when-let [errors (heroes/remove-achievement (-> req :params :id) (:body req))]
                                    {:errors errors}))}}

Resource Creation and Relationships

If you require that certain relationships are also part of the request when creating a new object, it is possible to specify these relationships with argo's CreateRequest.

For example, supposing we have a resource for achievements which relate to a hero's accomplishments. We may wish that an achievement can only be created by also specifying the hero who accomplished the feat. For example:

(s/defschema Achievement {:name (s/named s/Str "achievement name must be a string")})

(def NewAchievement
  (CreateRequest :achievements Achievement
  {:name :hero :type :heroes})) ;; :type :heroes for to-one relationship or :type [:heroes] for to-many.

Now when validating the request body for new achievements, the client will need to include the hero relationship as part of the request.

APIs

In order to create a new API use argo's defapi macro. This can then be set as your main ring handler.

It is possible to use the following keys in the api implementation:

  • :resources a vector or sequence of the resources to include in the api.
  • :middleware a vector or sequence of ring middleware to include on all endpoints.
  • :base-url can be used to prepend a base-url to all urls generated.
(require '[argo.core :refer [defapi]]
         '[myapi.resources :refer [heroes achievements]])  ; assuming a resource has been created for both heroes and achievements using defresource.

(defapi my-api {:resources [heroes achievements]})

In this case, we could now add my-api as the ring handler in our project.clj file.

Example

An example implentation is provided.

To run the example locally use lein example.

Let's add some data:

curl -XPOST \
  localhost:3000/v1/heroes \
  -H Content-Type:application/vnd.api+json \
  -d '
  {
    "data": {
      "type": "heroes",
      "attributes": {
        "name": "Jason",
        "birthplace": "Iolcos"
      }
    }
  }'; echo

You should see the following response:

{
    "data": {
        "attributes": {
            "birthplace": "Iolcos",
            "created": "2015-07-09T06:42:33Z",
            "name": "Jason"
        },
        "id": "1",
        "links": {
            "self": "/v1/heroes/1"
        },
        "relationships": {
            "achievements": {
                "links": {
                    "related": "/v1/heroes/1/achievements"
                }
            }
        },
        "type": "heroes"
    }
}

Now enter

curl -XPOST \
  -H Content-Type:application/vnd.api+json \
  localhost:3000/v1/achievements \
  -d '
  {
    "data": {
      "type": "achievements",
      "attributes": {
        "name": "Acquisition of the Golden Fleece"
      },
      "relationships": {
        "hero": {
          "data": {"type": "heroes", "id": "1"}
        }
      }
    }
  }'; echo

This should return the following reponse.

{
    "data": {
        "attributes": {
            "created": "2015-07-09T06:25:23Z",
            "name": "Acquisition of the Golden Fleece"
        },
        "id": "1",
        "links": {
            "self": "/v1/achievements/1"
        },
        "relationships": {
            "hero": {
                "links": {
                    "related": "/v1/achievements/1/hero"
                }
            }
        },
        "type": "achievements"
    }
}

You should now be able to make GET requests to the following endpoints and receive responses with data.

  • /v1/heroes
  • /v1/heroes/1
  • /v1/heroes/1/achievements
  • /v1/achievements
  • /v1/achievements/1
  • /v1/achievements/1/hero

You can experiment with adding more data and trying other request methods such as OPTIONS, PATCH and DELETE.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].