npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

another-rest-client

v0.7.0

Published

Simple REST API client that makes your code lesser and more beautiful than without it.

Downloads

176

Readme

= another-rest-client image:https://travis-ci.org/Amareis/another-rest-client.svg?branch=master[Build Status,link=https://travis-ci.org/Amareis/another-rest-client]

Simple REST API client that makes your code lesser and more beautiful than without it.

There is some rest clients - https://github.com/marmelab/restful.js[restful.js], https://github.com/cujojs/rest[cujojs/rest] or https://github.com/lincolnloop/amygdala[amygdala] - so why you need another rest client? Because with it your code less and more beautiful than without it or with any analogs. Also, its code really simple - less than 200 sloc and (almost) without magic, so you can just read it (and fix, may be?) if something go wrong.

To prove my words, here is an minimal working code (you can explore more examples https://github.com/Amareis/another-rest-client/tree/master/examples[here]):

And it works with typescript!

[source,typescript]

import {RestClient} from 'another-rest-client'

const api = new RestClient('https://api.github.com').withRes({ repos: 'releases', } as const)

api.repos('Amareis/another-rest-client').releases('latest').get().then((release: any) => { console.log(release); document.write('Latest release of another-rest-client:'); document.write('Published at: ' + release.published_at + ''); document.write('Tag: ' + release.tag_name + ''); });

== Installation

Library is available with npm:

[source,shell]

npm install another-rest-client

or

yarn add another-rest-client

Now, add it in script tag or require it or import it:

[source,js]

const {RestClient} = require('another-rest-client'); import {RestClient} from 'another-rest-client'

ATTENTION: If you want to use another-rest-client with node.js, you must define XMLHttpRequest before import (https://github.com/driverdan/node-XMLHttpRequest[see here]):

[source,js]

global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;

== Usage

[source,js]

const api = new RestClient('https://example.com');

And here we go! First, let's define resources, using res method:

[source,js]

api.res('cookies'); //it gets resource name and returns resource api.res(['cows', 'bees']); //or it gets array of resource names and returns array of resources api.res({ //or it gets object and returns object where resource is available by name dogs: [ 'toys', 'friends'], cats: 0, humans: 'posts' }); /* last string is equal to: api.res('dogs').res(['toys', 'friends']); api.res('cats'); api.res('humans').res('posts'); */

Now we can query our resources using methods get (optionally gets query args), post, put, patch (gets body content) and delete. All these methods returns promise, that resolves with object that given by server or rejects with XMLHttpRequest instance:

[source,js]

api.cookies.get(); //GET https://example.com/cookies api.cookies.get({fresh: true}); //GET https://example.com/cookies?fresh=true api.cookies.get({'filter[]': 'fresh'}, {'filter[]': 'taste'}); //GET https://example.com/cookies?filter%5B%5D=fresh&filter%5B%5D=taste

//POST https://example.com/cows, body="{"color":"white","name":"Moo"}" api.cows.post({color: 'white', name: 'Moo'}).then((cow) => { console.log(cow); //just object, i.e. {id: 123, name: 'Moo', color: 'white'} }, (xhr) => { console.log(xhr); //XMLHtppRequest instance });

If you want query single resource instance, just pass it id into resource:

[source,js]

api.cookies(42).get(); //GET https://example.com/cookies/42

//GET https://example.com/cookies/42?fields=ingridients,baker api.cookies(42).get({fields: ['ingridients', 'baker']});

api.bees(12).put({state: 'dead'}); //PUT https://example.com/bees/12, body="{"state":"dead"}" api.cats(64).patch({age: 3}); //PATCH https://example.com/cats/64, body="{"age":3}"

You can query subresources easily:

[source,js]

api.dogs(1337).toys.get(); //GET https://example.com/dogs/1337/toys api.dogs(1337).friends(2).delete(); //DELETE https://example.com/dogs/1337/friends/2

//POST https://example.com/humans/me/posts, body="{"site":"habrahabr.ru","nick":"Amareis"}" api.humans('me').posts.post({site: 'habrahabr.ru', nick: 'Amareis'});

You can use url resource method to get resource url:

[source,js]

api.dogs.url() === '/dogs'; api.dogs(1337).friends(1).url() === '/dogs/1337/friends/1';

And, of course, you always can use ES6 async/await to make your code more readable:

[source,js]

const me = api.humans('me'); const i = await me.get(); console.log(i); //just object, i.e. {id: 1, name: 'Amareis', profession: 'programmer'} const post = await me.posts.post({site: 'habrahabr.ru', nick: i.name}) console.log(post); //object

== TypeScript

Library infer types from schema, passed to res. But it returns new resource (or array or object), so to use it correctly, you need to use withRes method, which returns modified original resource:

[source,typescript]

let api = new RestClient('https://api.github.com').withRes({ repos: 'releases', } as const) // as const needed to infer resources names

// correctly infer all this subresources! api.repos('Amareis/another-rest-client').releases('latest').get()

You can then add more resources reusing already typed resource:

[source,typescript]

api = api.withRes('additional-resource')

Custom shortcuts currently not working with TypeScript!

== Events

RestClient use https://github.com/allouis/minivents[minivents] and emit some events:

  • request - when open XMLHttpRequest, but before send.
  • response - when get server response.
  • success - when get server response with status 200, 201 or 204.
  • error - when get server response with another status.

All events gets current XMLHttpRequest instance.

Often use case - authorization:

[source,js]

api.on('request', xhr => { xhr.setRequestHeader('Authorization', 'Bearer xxxTOKENxxx'); });

Also, returns by get, post, put, patch and delete Promise objects also emit these events, but only for current request.

[source,js]

api.dogs(1337).toys.get().on('success', console.log.bind(console)).then(toys => "..."); //in log will be xhr instance api.dogs(1337).toys.get().then(toys => "..."); //log is clear

You can use events to set responseType XMLHttpRequest property, to handle binary files (and you can compose it with custom decoders, as described below, to automatically convert blob to File object):

[source,js]

api.files('presentation.pdf').get().on('request', xhr => xhr.responseType = 'blob').then(blobObj => "...");

== Configuration

All the examples given above are based on the default settings. If for some reason you are not satisfied, read this section.

All configuration is done using the object passed to the constructor or method conf. Some options are also duplicated by optional methods arguments.

conf returns full options. If you call it without parameters (just conf()), it gives you current options.

[source,js]

console.log(api.conf()); /* Defaults: { "trailing": "", "shortcut": true, "shortcutRules": [], "contentType": "application/json", "encodings": { "application/x-www-form-urlencoded": {encode: encodeUrl}, "application/json": {encode: JSON.stringify, decode: JSON.parse} } }*/

If you want change RestClient host (lol why?..), you can just:

[source,js]

api.host = 'https://example2.com';

=== Trailing symbol

Some APIs require trailing slash (for example, this is the default behavior in the django-rest-framework). By default another-rest-client doesn't use any trailing symbol, but you can change this:

[source,js]

const api = new RestClient('https://example.com', {trailing: '/'}); //or api.conf({trailing: '/'});

Of course, you can pass all you want ({trailing: '/i-have-no-idea-why-you-want-this-but-you-can/'}).

=== Shortcuts

Shortcuts - resources and subresources, that accessible as parent resource field:

[source,js]

api.cars === undefined; const cars = api.res('cars'); api.cars === cars; //api.cars is shortcut for 'cars' resource

By default, another-rest-client will make shortcuts for defined resources. This behavior can be disabled in three ways:

[source,js]

api.sounds === undefined

//first way const api = new RestClient('https://example.com', {shortcut: false}); //or, second way api.conf({shortcut: false}); //or, third way const sounds = api.res('sounds', false);

//and, still... api.sounds === undefined;

First two ways disables shortcuts globally - on all resources and subresources. Third way disables shortcuts locally - in one res call. Also, with third way you can locally enable shortcuts (pass true as second res argument) when globally they are disabled.

Local disable of shortcuts can solve some name conflicts (when resource shortcut overwrites some method), but, probably, you will not be affected by this.

It is strongly recommended do not disable the shortcuts, they greatly enhance code readability.

You can also add custom shortcuts for resources via rules. Those can be configured via the shortcutRules array in the options. When a resource is added all rules will be invoked with the resource name as argument. If the return value is a non-empty string, it will serve as an additional shortcut.

Have a look at this example which will convert strings with dashes into their camel-case counterpart to serve as additional shortcut:

[source,js]

const DASH_REG = /(-)(.)/g; function dashReplace(resourceName) { return resourceName.replace(DASH_REG, (match, p1, p2) => p2.toUpperCase()); }

const api = new RestClient('https://example.com', {shortcutRules: [ dashReplace ]}); api.res('engine-rest'); api['engine-rest']; // standard shortcut api.engineRest; // custom shortcut to improve readability

=== Request content type

When you call post, put or patch, you pass an object to be encoded into string and sent to the server. But how it will be encoded and what Content-Type header will be set? By default - in json (application/json), using JSON.stringify. To change this behavior, you can manually set request content type:

[source,js]

const api = new RestClient('https://example.com', {contentType: 'application/x-www-form-urlencoded'}); //or by conf api.conf({contentType: 'application/x-www-form-urlencoded'}); //or by second argument in 'post', 'put' or 'patch' api.cookies.post({fresh: true}, 'application/x-www-form-urlencoded');

By default RestClient can encode data in application/json and application/x-www-form-urlencoded. You can add (or replace defaults with) your own encoders:

[source,js]

const opts = { contentType: 'application/x-my-cool-mime', encodings: { 'application/x-my-cool-mime': { encode: (objectPassedToPostPutOrPatch) => { //... return encodedToStringObject; } } } } const api = new RestClient('https://example.com', opts); //or by conf api.conf(opts);

If there is no suitable encoder, passed object will be passed to the XMLHttpRequest.send without changes.

=== Response content type

When server answers, it give Content-Type header. another-rest-client smart enough to parse it and decode XMLHttpRequest.responseText into object. By default it can decode only application/json using JSON.parse, but you can add your own decoders:

[source,js]

const opts = { encodings: { 'application/x-my-cool-mime': { decode: (stringFromXhrResponseText) => { //... return decodedFromStringObject; } } } } const api = new RestClient('https://example.com', opts); //or by conf api.conf(opts);

If there is no suitable decoder (or server given't Content-Type header), gotten XMLHttpRequest.response will be passed to Promise.resolve without changes.

Of course, you can combine encoders and decoders for single MIME:

[source,js]

const opts = { contentType: 'application/x-my-cool-mime', encodings: { 'application/x-my-cool-mime': { encode: (objectPassedToPostPutOrPatch) => { //... return encodedToStringObject; }, decode: (stringFromXhrResponseText) => { //... return decodedFromStringObject; } } } }

const api = new RestClient('https://example.com', opts); //or by conf api.conf(opts);

== Contributing

That's easy:

[source,bash]

git clone https://github.com/Amareis/another-rest-client.git cd another-rest-client yarn echo "//Some changes..." >> src/rest-client.ts yarn build && yarn test