contiamo-rest-client

Simple RESTful JS client to interact with nested resources in a given backend.

Usage no npm install needed!

<script type="module">
  import contiamoRestClient from 'https://cdn.skypack.dev/contiamo-rest-client';
</script>

README

Contiamo REST Client

Build Status

This REST client attempts to communicate with a backend API, interacting in a declarative way.

Getting Started

First, you'll want to add the contiamo-rest-client to your project like so:

npm install contiamo-rest-client, alternatively yarn add contiamo-rest-client if you're into yarn.

Then, your code needs a couple of building blocks:

Block 1 - an API blueprint

Consider the following REST endpoint:

GET https://api.myapp.com/tenants/123/bundles

The above URL signals two things:

  1. There is a collection of resources available at /tenants.
  2. There is a more specific tenant (#123) that we want to retrieve bundles for.

Expressed as a blueprint that this client understands, this would look like:

import { TenantCollection, BundleCollection } from "./collections"

export default {
    key: 'tenants',
    collection: TenantCollection,
    path: 'tenants',
    children: [
        {
            key: 'bundles',
            collection: BundleCollection,
            path: 'bundles',
        }
    ]
}

From the above snippet, we see that the BundlesCollection is a child of TenantsCollection, similar to our URL structure.

But what is TenantsCollection?

Block 2 - Collections and Resources

Consider:

import { 
    Collection,
    Listable,
    Fetchable,
    Destroyable,
    Resource
} from "contiamo-rest-client"

export class TenantsCollection extends Listable(Collection) {
    constructor() {
        this.resource = Fetchable(Destroyable(Resource))
    }
}

From the above snippet, we see the proper intended usage of this client. The client exposes a Collection and a Resource onto which certain privileges can be applied: Fetchable, Listable, Destroyable, Editable, Retrievable, etc. These privileges, under the hood, really just map to their respective HTTP verbs.

The API blueprint composes these collections together.

Putting It All Together: Instantiating the Client

Consider:

import { Client } from "contiamo-rest-client"
import blueprint from "./api"

const myClient = new Client(blueprint, {
    apiBase: 'https://whatever.rest/',
})

The client then exposes an API as so:

/**
 * Keeping the URL in mind,
 * GET https://api.myapp.com/tenants/123/bundles
 */

const getBundles = async () => await myClient
    .children('tenants')
    .build('123')
    .children('bundles')
    .list()

This would fetch a list of bundles for tenant 123. To fetch a specific bundle resource, one could instead write .children('bundles').build('456').fetch().

The API of this client is self-documenting when using TypeScript and an autocomplete feature like IntelliSense.

Privileges

Below is a brief overview of the various privileges (.list(), .fetch(), etc.) available to Collections and to Resources.

Collection Privileges

Listable(Collection)

This privilege lets a collection (or list) be retrieved. (GET)

Usage: Collection.list()

const listChildren = async () => await Client.children('myListableCollection').list()

Creatable(Collection)

This privilege lets a collection (or list) be created. (POST)

Usage: Collection.create()

const createCollection = async () => await Client.children('myListableCollection').create()

Resource Privileges

Fetchable(Resource)

This privilege lets a single resource be retrieved. (GET)

Usage: Resource.fetch()

const listChildren = async () => await Client.children('myListableCollection').build('123').fetch()

Modifiable(Resource)

This privilege lets a single resource be modified. (PUT)

Usage: Resource.modify()

const listChildren = async () => await Client.children('myListableCollection').build('456').modify({ 
    data: {
        updatedData: 'hello' 
    } 
})

Destroyable(Resource)

This privilege lets a single resource be deleted. (DELETE)

Usage: Resource.destroy()

const listChildren = async () => await Client.children('myListableCollection').build('789').destroy()

How It Works

Basically, the Client at the top of the .children().build().children().build().doSomething() chain is the single point of contact with a backend API. This is where the fetching happens. Everything else is just syntactic sugar that consults the blueprint in order to construct a path to query. When request is called on a Resource or a Collection, this method bubbles up to the top-level request method on Client, that does the actual server communication.

Contributing

  1. git clone git@github.com:Contiamo/rest-client.git
  2. yarn install
  3. code . (or atom . or slime . or whatever dude)

Look up and create issues, make PRs, spread the love. ❤️