Skip to Content

REST API Recommendations

Overview

This document provides guidance for developers looking to implement a Vinyl-compatible REST API.

For the purposes of this document, we will assume that the developer is focused on creating a CRUD REST service. Many of the principles used in a CRUD API will be applicable to other APIs. However a CRUD API will likely have the most comprehensive requirements that will interplay with Vinyl (paging, searching, filtering etc).

RESTful design principals

To the extent possible, Vinyl's REST API follows these RESTful principals:

  • Services are stateless.
  • Endpoints are modeled as resources.
  • GET operations are safe. A "safe" operation is one that does not have side effects. For instance, retrieving a list of customers does not change the list of customers.
  • DELETE operations are unsafe, but idempotent. However, whereas the first (successful) request to delete an item will return a 200 status code, the second request will return a 404.
  • POST operations are neither safe nor idempotent. Because of this, POST operations may contain partial data.
  • HTTP status codes indicate whether an error occurred.
  • Media types are used to perform content negotiation. At the moment, however, Vinyl only supports JSON (application/json) and UTF-8.

Vinyl does not adhere to all RESTful principals:

  • Resource request and response bodies are wrapped in an envelope. This allows Vinyl to include additional information, such as event messages and validation results.
  • Resource responses are not hypermedia: they do not include links to other resources.

Envelope

Vinyl REST API request and response bodies are wrapped in an envelope. Envelopes allow data to be sent to and from an API endpoint outside of the endpoint payload.

Request Body Envelope

The Vinyl REST API request body contains these envelope properties:

Property Name Description
item The endpoint payload.

Example JSON

{
  "item": {}
}

Response Body Envelope

The Vinyl REST API response body contains these envelope properties:

Property Name Description
message The success or failure message for the event.
status This is the (duplicated) HTTP status code.
validations An array of validation errors/warnings for the endpoint called.
item or items The endpoint payload - either a single item or collection of items.

Example JSON

{
  "message": "",
  "status": 200,
  "validations": [],
  "items": []
}

Validations

When errors are encountered, a validation object is added to the validations array in the response envelope. The validation object has the follow properties:

Property Name Description
message The validation message.
severity The severity of the validation:
  • error
  • warning
  • information
field The field referenced by the validation.

Example JSON

{
  "message": "",
  "status": 400,
  "validations": [
    {
      "message": "CustomerId is mandatory.",
      "severity": "error",
      "field": "customerId"
    },
    {
      "message": "CompanyName is mandatory.",
      "severity": "error",
      "field": "companyName"
    }
  ],
}

Core Operations

These are the basic operations that your REST service should support. Some services will of course choose to not implement certain methods (e.g. a read-only service would only implement Get Single/Get Many methods).

Get Single

The Get Single operation returns a single record. The identifier for the record is specified in the URL.

HTTP Method

GET

Example URL

https://example.com/rest/v1/sales/customers/b603b276-a9bf-4328-88ff-8994176c38d1

Example JSON

{
  "item": {
    "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
    "name": "John Henry",
    "address": "130 Plutonium Drive"
  }
}

Get Many

The Get Many operation returns many records for a collection. Oftentimes, this operation is used alongside pagination, to allow a collection of records to be perused.

If possible, a count of the records in the collection should be returned. This allows Vinyl to display the record count in the UI as well as informing the UI when the end of the collection has been reached.

HTTP Method

GET

Example URL

https://example.com/rest/v1/sales/customers

Example JSON

{
  "count": 2,
  "items": [
    {
      "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
      "name": "John Henry",
      "address": "130 Plutonium Drive"
    },
    {
      "customerId": "9775de33-08fc-4cd2-98ef-d91f3d5355b1",
      "name": "Sally Keith",
      "address": "4500 Neutrino Road"
    }
  ],
  // envelope properties
}

Create

The Create operation will create a new record. The identifier for the record exists within the JSON body.

Note the entire record is echoed back in the response. This is useful in cases where some fields are created or updated by the server (such as a record creation time stamp).

HTTP Method

POST

Example URL

https://example.com/rest/v1/sales/customers

Example Request Body JSON

{
  "item": {
    "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
    "name": "John Henry",
    "address": "130 Plutonium Drive"
  }
}

Example Response Body JSON

{
  "item": {
    "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
    "name": "John Henry",
    "address": "130 Plutonium Drive"
  }
  // envelope properties
}

Update

The Update operation will upate an existing record. The identifier for the record is specified in the URL.

Note the entire record is echoed back in the response. This is useful in cases where some fields are created or updated by the server (such as a record update time stamp).

HTTP Method

  • PUT - Used for a full update. All parameters of the record need to be specified.
  • POST - Used for a partial update. Only mandatory parameters of the record need to be specified.

Example URL

https://example.com/rest/v1/sales/customers/b603b276-a9bf-4328-88ff-8994176c38d1

Example Request Body JSON

{
  "item": {
    "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
    "name": "John Henry",
    "address": "130 Plutonium Drive"
  }
}

Example Response Body JSON

{
  "item": {
    "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
    "name": "John Henry",
    "address": "130 Plutonium Drive"
  }
  // envelope properties
}

Delete

The delete operation will delete a record. The identifier for the record is specified in the URL. No request body needs to be sent for a DELETE.

HTTP Method

DELETE

Example URL

https://example.com/rest/v1/sales/customers/b603b276-a9bf-4328-88ff-8994176c38d1

Example Response Body JSON

{
  "item": {
    "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
    "name": "John Henry",
    "address": "130 Plutonium Drive"
  }
}

Query Parameters

Get Many

At the collection level, your REST endpoint should support the following features.

Paging

For collections that contain many records, it is often necessary to support paging. In order to support paging, Vinyl defines the following parameters:

Query Parameter Description Example
$limit The maximum number of records to retrieve in one request. $limit=10
$offset From which record offset the fetch should begin. Offsets are zero based so specifying 0 will fetch the first record in the collection. $offset=10
$count A boolean indicating whether a collection count should be returned. In Vinyl, this parameter is considered false by default. $count=true

Sorting

Vinyl can support simple sorting of fields.

Query Parameter Description Example
$sort A comma-delimited list of field names to sort by. Prefix the field name with a dash (-) to sort the field in descending order. In the provided example, sort the collection by country, descending, and companyName, ascending. $sort=-country,companyName

Filtering

Vinyl provides support for a query filter string. The query filter string supports a subset of the OData 4.0 query string filter specification. For string comparisons, wildcards can be specified using the % character.

Query Parameter Description Example
$filter An OData 4.0 style query string that filters data $filter=country eq 'united%'
Operators
Comparison
Operator Description Example
eq Equal to the value. categoryId eq 42
neq Not equal to the value. categoryId neq 42
gt Greater than the value. price gt 100
lt Less than the value. price lt 100
ge Greater than or equal to the value. price ge 100
le Less than or equal to the value. price le 100
in Match any value in the list.
Added in Vinyl 3.2.
categoryId in (1, 2, 3)
Logical
Operator Description Example
and Logical AND price gt 100 and categoryId in (1,2,3)
Limitations
  • No arithmetic operators
  • No or/not logical operators
  • No grouping operators
  • No query functions
  • No parameter aliases

Vinyl provides a mechanism to search for records in a collection. This search operates across all searchable fields of the record.

Vinyl by default adds wildcards to the beginning and end of the search string.

Query Parameter Description Example
$q A search string to apply to all searchable columns of a record. $q=hello

Type Conventions

JSON Types

As a general rule, developers should prefer using the native built-in JSON types. Vinyl automatically maps native JSON types so direct use of numerics, booleans, strings, and nulls is encouraged.

Dates

Vinyl encodes dates using the ISO 8601 format. All dates are serialized in UTC.

Versioning

To handle future incompatibilities between REST API versions, Vinyl includes a version number directly in the REST URL.

Example URL

https://example.com/rest/v1/...

Optional Operations

Other operations which may be useful to include for a CRUD REST API.

New

The new operation is used to return record defaults. This is used in advance of creating a record for cases where some data may be pre-filled by the REST server.

HTTP Method

  • GET
  • POST.

Example URL

https://example.com/rest/v1/sales/customers(new)

Example Response Body JSON

{
  "item": {
    "customerId": null,
    "daysActive": 0
  }
  // envelope properties
}

JSON - Relational Tables Conversion

Vinyl provides a mechanism to convert between the internal relational table representation of data and JSON expected by REST endpoints.

Arrays to Tables

Each array in a JSON structure is considered a separate relational table within Vinyl.

As such, the following conversion would apply to an endpoint named "customers(get)":

customers(get) JSON

{
  "count": 2,
  "message": "Sample message",
  "status": 200,
  "items": [
    {
      "customerId": "b603b276-a9bf-4328-88ff-8994176c38d1",
      "name": "John Henry",
      "address": "130 Plutonium Drive"
    },
    {
      "customerId": "9775de33-08fc-4cd2-98ef-d91f3d5355b1",
      "name": "Sally Keith",
      "address": "4500 Neutrino Road"
    }
  ],
  // envelope properties
}

Resulting Table Structure

"customers(get) "

Count Message Status
2 Sample message 200

"customers(get)/items"

customerid name address
9775de33-08fc-4cd2-98ef-d91f3d5355b1 Sally Keith 4500 Neutrino Road
b603b276-a9bf-4328-88ff-8994176c38d1 John Henry 130 Plutonium Drive

Writing Data

Currently, Vinyl only supports writing to a single table during an event. Since each JSON array is considered a separate table, writing an entire object that comprises multiple arrays in a single Vinyl event may not be supported.

As a result, look to minimize the usage of JSON arrays where possible, or support writing the arrays in an object in a separate REST API endpoint.

For example a "customer" object may contain multiple addresses. Having an endpoint to write the customer object, and another endpoint to write the addresses allows Vinyl to more easily integrate with your REST API.