siren-writer

A Siren Hypermedia Object Generator

node-siren-writer

A generator for siren hypermedia API responses.

var writer = require("siren-writer");
var siren = writer('http://api.x.io');
 
var entity = siren({
  class: 'order',
  properties: {
    orderNumber: 42,
    itemCount: 3,
    status: 'pending'
  },
  entities: [
    {
      class: [ 'items', 'collection' ],
      rel: 'http://x.io/rels/order-items',
      href: 'orders/42/items'
    },
    {
      class: [ 'info', 'customer' ],
      rel: 'http://x.io/rels/customer',
      properties: {
        customerId: 'pj123',
        name: 'Peter Joseph'
      },
      links: {
        rel: 'self',
        href: 'customers/pj123'
      }
    }
  ],
  actions: {
    name: 'add-item',
    title: 'Add Item',
    method: 'POST',
    href: 'orders/42/items',
    type: 'application/x-www-form-urlencoded',
    fields: [
      { name: 'orderNumber', type: 'hidden', value: '42' },
      { name: 'productCode', type: 'text' },
      { name: 'quantity', type: 'number' }
    ]
  },
  links: [
    {
      rel: 'self',
      href: 'orders/42'
    },
    {
      rel: 'previous',
      href: 'orders/41'
    },
    {
      rel: 'next',
      href: 'orders/43'
    }
  ]
});
 
console.log(entity);

will produce the example from the Siren homepage:

{
  "class": [ "order" ],
  "properties": {
      "orderNumber": 42,
      "itemCount": 3,
      "status": "pending"
  },
  "entities": [
    {
      "class": [ "items", "collection" ],
      "rel": [ "http://x.io/rels/order-items" ],
      "href": "http://api.x.io/orders/42/items"
    },
    {
      "class": [ "info", "customer" ],
      "rel": [ "http://x.io/rels/customer" ],
      "properties": {
        "customerId": "pj123",
        "name": "Peter Joseph"
      },
      "links": [
        { "rel": [ "self" ], "href": "http://api.x.io/customers/pj123" }
      ]
    }
  ],
  "actions": [
    {
      "name": "add-item",
      "title": "Add Item",
      "method": "POST",
      "href": "http://api.x.io/orders/42/items",
      "type": "application/x-www-form-urlencoded",
      "fields": [
        { "name": "orderNumber", "type": "hidden", "value": "42" },
        { "name": "productCode", "type": "text" },
        { "name": "quantity", "type": "number" }
      ]
    }
  ],
  "links": [
    { "rel": [ "self" ], "href": "http://api.x.io/orders/42" },
    { "rel": [ "previous" ], "href": "http://api.x.io/orders/41" },
    { "rel": [ "next" ], "href": "http://api.x.io/orders/43" }
  ]
}

Creates a new siren writer with the given base as the base URL for things like rel and href throughout an entity.

The returned function is the entire API. It returns an object that can be serialized as a JSON response.

// express 
res.json(siren(/* options */));
 
// koa 
this.body = siren(/* options */);

Generally-speaking, this API avoids performing magic. It wants you to be explicit, only accepting objects instead of positional arguments. However, there are a few ways that this API improves upon generating the response entirely from scratch:

  • ensures that single values are converted into arrays where they're required (eg: class, rel, etc)
  • flattens nested arrays into a single array, particularly for things like entities where you could be merging several different types for a single response
  • automatically resolves URLs relative to the base URL (eg: href and any rel values that aren't defined by IANA)
  • throws errors when you are missing things that are explicitly required, such as the href or rel for a link
  • throws errors when certain values are outside the list of supported things, such as method and field type

The keys in options match the spec, so use that as a reference when building your configuration options. Some things to be aware of:

  • anything that isn't part of the spec will be discarded
  • as mentioned before, don't be too concerned with arrays if you're only setting a single value
  • don't be afraid to nest arrays, they will be flattened
  • include the trailing / in your base, especially when using a pathname
  • avoid using a leading / in your relative URLs, as it will behave unexpectedly if your base has a pathname as part of it. (ie: http://example.com/api/)
  • this library will throw exceptions in the cases mentioned previously