fetch-decorators

0.0.7 • Public • Published

fetch-decorators

npm package Build Status Commitizen friendly Code Climate Test Coverage npm package

A set of composable ES7 decorators around the fetch api

Automate things request and response body parsing so you don't have to.

No dependency (oh, except a fetch polyfill maybe)

Usage TL;DR:

npm i -S fetch-decorators
class Messages {
  @extractJson
  @bodify
  @fetchify({method: 'POST'})
  post(userId) {
    return `/users/${userId}/messages`;
  }
}
 
const messages = new Messages();
 
messages.post('ArnaudRinquin')({
  content: 'Hello World',
  public: true,
  draft: false,
}).then(({response, data}) => {
  // response === the original fetch response
  // data === the JSON object returned by the server
});
 

Decorators

  • @fetchify: decorates a function returning a url to a fetch call with your options.
  • @bodify: prepare passed data (and extra options) into fetch-ready body options.
  • @extractJson, @extractText, @extractBlob: decorates a function returning a Response to extract its result as json, text or blob.
  • @extractAuto: decorates a function returning a Response to extract its result automatically based on response Content-Type header.

These decorators have been designed to be composable, give it a try!

@fetchify(options:?object)

This helper wraps the original function into a fetch call so it may just return a string, and then be called with optional data, headers, options.

(originalArgs) => url:string

becomes:

(originalArgs) => (options:?object) => fetchResponse:promise

import { fetchify } from 'fetch-decorators';
 
class Users {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
 
  @fetchify()
  get(userId) {
    return `${this.baseUrl}/users/${userId}`;
  }
 
  @fetchify({
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    }
  })
  create() {
    return `${this.baseUrl}/users`;
  }
}
 
const userApi = new UserApi('/api');
 
userApi.createUser()({
  body: JSON.stringify({
    firstName: 'Walter',
    lastName: 'White',
  }
})).then(function(response){
  // Regular `fetch` response
);
 
userApi.getUser('fakeUserId123')().then(function(response){
  // Regular `fetch` response
});

@bodify

Takes body data and options and calls the decorated function with a proper fetch options object where options.body is passed data as a string.

(options) => orignalResult

becomes:

(originalArgs) => (data:object, extraOptions:?object) => orignalResult(options)

import { bodify } from 'fetch-decorators';
 
class Messages {
  @bodify
  create(userId) {
    return function(options) {
      return fetch(`/api/users/${userId}/messages`, options);
    };
  }
}
 
const messages = new Messages();
const messages = {
  content: 'Hello',
  draft: false,
};
const options = { method: 'POST' };
 
users.create('fakeUserId')(message, options).then(function(response){
  // response === the original fetch response
});

@extractJson, @extractText, @extractBlob

These decorators wrap functions returning a fetch promise with the matching Response extraction function.

(originalArgs) => fetchResponse:promise

becomes:

(originalArgs) => (options:?object) => extractionResult:promise

where the extractionResult promise resolves with : {response:Response, data:any}

import { extractJson } from 'fetch-decorators';
 
class Users {
  @extractJson
  get(userId) {
    return (options) => fetch(`/api/users/${userId}`, options);
  }
}
 
const users = new Users();
 
users.get('userId123')().then(function({response, data}){
  // response === the original fetch response
  // data === the extracted data, here a `user` JSON object
});
 

@extractAuto

This extractor has the same signature and behaviour as other extractors but will use the Reponse Content-Type header to determine the right Response method to use for data extraction.

Content types are matched this way:

'application/json': 'json'
'text/plain': 'text'
'default': 'blob'

Composition

All these decorators where designed so it's easy to use them together, just stack them! Note: obviously, the order matters.

import {
  fetchify,
  bodify,
  extractJson,
} from 'fetch-decorators';
 
class Messages {
  @extractJson
  @bodify
  @fetchify({method: 'POST'})
  post(userId) {
    return `/users/${userId}/messages`;
  }
 
  // Approximate equivalent without decorators
  // Thanks to ES6, the volume of code is roughly the same
  // But the complexity is higher and you'll probably
  // have a lot of code duplication
  mehPost(userId, data, extraOptions) {
    return fetch(`/users/${userId}/messages`, {
      method: 'POST',
      ...extraOptions,
      body: JSON.stringify(data),
    }).then((response) => response.json());
  }
}
 
const messagesApi = new Messages();
 
// Request body, as an object
const message = {
  content: 'Hello World',
  public: true,
  draft: false,
};
 
// Some extra options
const authHeaders = {
  headers: {
    'X-AUTH-MUCH-SECURE': '123FOO456BAR',
  },
};
 
messagesApi.post('ArnaudRinquin')(message, authHeaders).then(({response, data}) => {
  // response === the original fetch response
  // data === the JSON object returned by the server
});

FAQ

Is it just syntactic sugar?

Yes.

I have a more complex case where my [ headers are dynamic | I have to change fetch options | whatever ]. How do I do?

Then you should manually write your call to fetch instead of shoehorning these decorators in your code.

/fetch-decorators/

    Package Sidebar

    Install

    npm i fetch-decorators

    Weekly Downloads

    1

    Version

    0.0.7

    License

    MIT

    Last publish

    Collaborators

    • arnaudrinquin