Giter Site home page Giter Site logo

amareis / another-rest-client Goto Github PK

View Code? Open in Web Editor NEW
174.0 16.0 17.0 227 KB

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

License: MIT License

JavaScript 52.40% TypeScript 47.60%
rest rest-client javascript typescript

another-rest-client's Introduction

another-rest-client

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

There is some rest clients - restful.js, cujojs/rest or amygdala - so why you need another rest client? First, because all of this is not maintained anymore :) But also, because with it your code less and more beautiful than without it or with any analogs. Also, its code really simple - less than 300 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 here):

And it works with 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:<br>')
    document.write('Published at: ' + release.published_at + '<br>')
    document.write('Tag: ' + release.tag_name + '<br>')
})

Installation

Library is available with npm:

npm install another-rest-client
# or
yarn add another-rest-client

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

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 (see here):

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

Usage

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

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

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:

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:

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:

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:

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:

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:

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:

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

Custom shortcuts currently not working with TypeScript! And shorcuts always will be in typings, even if they are disabled.

Events

RestClient use 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:

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.

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):

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.

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:

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:

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

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

Shortcuts

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

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:

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:

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:

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:

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:

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:

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:

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

another-rest-client's People

Contributors

amareis avatar felixlinker avatar vixalien avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

another-rest-client's Issues

Abort XHR?

In current project I heavily use your library. Sometimes I need to abort opened xhr requests.

I think you could add one time event listeners. Look at this

...

let openedXHR = [];
api.once('request', (xhr) => {
    openedXHR.push(xhr);
})
api.res('account').res('balance').get().then(...)

openedXHR.forEach((xhr) => xhr.abort())

I can't use request event, because RestClient created once and reused everywhere.

💡Can you add retry config to your amazing work?

Just like this (maybe like your awesome fluent api)
In this project: https://github.com/JustinBeckwith/retry-axios
and add methods like get, put, and so on doing their job.
source: https://github.com/JustinBeckwith/retry-axios
`raxConfig: {
// Retry 3 times on requests that return a response (500, etc) before giving up. Defaults to 3.
retry: 3,

// Retry twice on errors that don't return a response (ENOTFOUND, ETIMEDOUT, etc).
noResponseRetries: 2,

// Milliseconds to delay at first.  Defaults to 100. Only considered when backoffType is 'static'
retryDelay: 100,

// HTTP methods to automatically retry.  Defaults to:
// ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT']
httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT'],

// The response status codes to retry.  Supports a double
// array with a list of ranges.  Defaults to:
// [[100, 199], [429, 429], [500, 599]]
statusCodesToRetry: [[100, 199], [429, 429], [500, 599]],

// If you are using a non static instance of Axios you need
// to pass that instance here (const ax = axios.create())
instance: ax,

// You can set the backoff type.
// options are 'exponential' (default), 'static' or 'linear'
backoffType: 'exponential',

// You can detect when a retry is happening, and figure out how many
// retry attempts have been made
onRetryAttempt: err => {
  const cfg = rax.getConfig(err);
  console.log(`Retry attempt #${cfg.currentRetryAttempt}`);
}

}`

Also there's another that has it's magic hidden like this:
https://github.com/softonic/axios-retry

Thanks in advance beacuse your awesome work

What is about camelize / decamelize config?

As I can see I cant decamelize request params without own wrapper like

this.api['groups'].post(decamelizeKeys(data)).then(response => {
  return camelizeKeys(response)
})

for each action

Generate request with non-unique keys

Hi there and thanks for this library!

Is it possible to create a request with non-unique keys?

In the backend i want to use php-crud-api which has a filter feature which allows multiple filter[] tags to combine conditions where the request would look something like

this.REST.endpoint.get({"filter[]": "name_id,eq," + 1337, "filter[]": "category_id,ge," + 4223, columns: "name, category, timestamp", order: "timestamp, asc" })...

results in the following XHR request:

XHR finished loading: GET "http://localhost/api.php/endpoint?filter%5B%5D=category_id%2Cge%2C4223&columns=name%2C%20category%2C%20timestamp&order=timestamp%2C%20asc"

so the first filter[] part gets truncated.

Simple workaround i found was to explicetly name the indices of the array like

this.REST.endpoint.get({"filter[0]": "name_id,eq," + 1337, "filter[1]": "category_id,ge," + 4223, columns: "name, category, timestamp", order: "timestamp, asc" }).

but i still think itd be great if the library could take care of that or forward the URL unmodified.

Null response

Firstly i like your library its small and works (i tried jquery.rest but it seems to force parameters on posts), but yours worked straight off, although i do slightly prefer how they support the .post(obj).done(fnDone).fail(fnFail) appraoch rather than .then(fnDone, fnFail) but all in all i still prefer yours.

Now the tiny issue (and fix?):

In WebAPI if you "return Ok()" or StatusCode 204 (no content) it doesnt set a content type, so there is an exception when your library tries to parse the content type ("Cannot read property 'split' of null(…)" on line 140).

This small mod appears to fix it without any unforeseen issues (though you can fix it in anyway you like obviously, since i do not have the time to learn to submit patches just wanted to make the fix as painless as possible).

from
var responseContentType = xhr.getResponseHeader('Content-Type').split(';')[0];
to
var responseContentType = (xhr.getResponseHeader('Content-Type') || "").split(';')[0];

Rework api for better TypeScript support and overall simplicity

  • Replace custom shortcuts with some inline field which can be inferred by TS (and maybe remove disabling standard shortcuts at all)
    api.res({'some-field: {SHORTCUT: 'someField}}).someField.get()
  • Remove res completely or make it working just like withRes
  • Remove cloning all resources when id is passing - need prev item to be done first
  • Maybe add some possibility to add types for api response (or runtime typechecking with existing libs)
  • Think about #12

Cookie

I don't see any way to set withCredentials to true to make request with cookies

Concurrent requests with events

If you need, for example, get response headers, you can do so:

let pageLinks = {}
api.on('success', x => pageLinks.links = x.getResponseHeader('Link'))
let commits = await api.repos(action.repo.full_name).commits.get()
api.off('success')
console.log(pageLinks.links)

But it can be broken easily if another request emit success after this successevent, but before console.log. So we need method for attach event to strictly defined request, it's also fits #3

ProgressEvent

Is it possible to get the progress of a (large) JSON response?

xhr spec
xhr example

Update:
I've tried implementing the example on whatwg.org, but no success. I don't see any calls to onprogress in my browser console. Would this be something provided by the server? (I'm using Python/Flask, and doing some research there also).

api.api().myapi()
    .get()
    .on('response', function(res) {
        console.log(res.onprogress) // returns 'null'
        res.onprogress = function(pe) {
           console.log(pe); // not reached
           if (pe.lengthComputable) {
               console.log(pe.loaded) // not reached
           }}})
     .then(function (_json) { do_stuff(_json) })

Support for blob (or any other) response type

Nice rest client!
But it only works with string response type, because of it's set by default.
So, when we try to work with blob (e.g. trying to get pdf from server), the blob from response goes incorrenct because of wrong xhr.responseType.

To get the pdf get viewed correctly, i had to set responseType manually and handle it in success hook:

    rest
      .on( 'request', xhr => {
        xhr.responseType = 'blob'
      } )
      .on( 'success', xhr => {
        const blob = new Blob( [ xhr.response ], { type: 'application/pdf' } )
        FileSaver.saveAs( blob, `report.pdf` )
      } )
      .on( 'error', xhr => {
      } )
      [ REPORTS ]
      .get( report )

Would be nice to configure the xhr.responseType property and get the results from xhr.reponse, not from xhr.reposneText as it is now.

Not getting expected headers in XmlHttpRequest during a response

Hello,

I use the following snippet:

    const api = new RestClient("http://localhost:8080/mgag/api");
    api.res("users");
    api.users.post(data).then(function (evt) {
        console.log(evt);
    }, function (other) {
        alert("Unable to subscribe");
        console.log(other);
        console.log(other.getAllResponseHeaders());
    });

Unfortunately, the other parameter doesn't contain any useful headers even if the server sends them.

What I'm getting in Mozzilla Developper Console:
image

image

I want to grab the "Cause" header here.

Please note that I use CORS.

Axios instead XMLHttpRequest

I have some troubles with Authorization headers in XMLHttpRequest using Auth0. When I try to renew token from Auth0 after page loaded with this code:

api.on('request', xhr => {
  xhr.setRequestHeader('Authorization', `Bearer ${authResult.accessToken}`);
})

I expect that new token will replace the old one, but it's add like: Bearer OLDTOKEN, NEWTOKEN.

Can you help with replace XMLHttpRequest or provide example how to use tokens and when and how I should replace it?

RestClient.conf() refactoring

Hi! Very nice little and useful library!
Just as a proposal: why not to use Object.assign() method in your RestClient.conf()?
For example, you can move your default options to the separate file, import it, and then do something like this:

import settings from './settings'
....
conf(options={}) {
  this._opts = Object.assign({}, settings, options)
}

Thanks and good luck!

Firefox: SyntaxError: JSON.parse: unexcepted...

var r = new RestClient('http://127.0.0.1:8080', {contentType: 'json'});
r.res('test')
r.test('ttt').get().then(function(k){alert(k)})

Chromium:
It works properly!

Firefox:
http://127.0.0.1:8080/test/ttt works properly. but when I use another-rest-client, It says:
SyntaxError: JSON.parse: unexcepted end of data at line 1 column 1 of the JSON data
but when I click on "edit and resend" and click on "send" without any change on headers, it return correct json in the response section without "SyntaxError: JSON.parse: unexcepted end of data at line 1 column 1 of the JSON data"

I'm using Bottle on Python as backend.

SyntaxError: Unexpected token u in JSON at position 0

Hi there,

I tried using this library and following code yielded an error:

const RestClient = require('another-rest-client');
global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;

let api = new RestClient('http://localhost:8082/engine-rest');
api.on('error', function(err) {
    console.log('err');
});
api.res('engine');
api.engine.get().then(function(result) {
    console.log(result);
}, function(reason) {
    console.log(reason);
});

When debugging the code in Visual Studio Code the promise resolved successfully with result being undefined. Also the error callback was not called.
The error displayed on the console is the following:

SyntaxError: Unexpected token u in JSON at position 0
    at parse (native)
    at safe (c:\Users\felix\Desktop\js-test\node_modules\another-rest-client\rest-client.js:80:17)
    at _createClass.value.xhr.onreadystatechange (c:\Users\felix\Desktop\js-test\node_modules\another-rest-client\rest-client.js:157:67)
    at exports.XMLHttpRequest.dispatchEvent (c:\Users\felix\Desktop\js-test\node_modules\xmlhttprequest\lib\XMLHttpRequest.js:591:25)
    at setState (c:\Users\felix\Desktop\js-test\node_modules\xmlhttprequest\lib\XMLHttpRequest.js:610:14)
    at IncomingMessage.<anonymous> (c:\Users\felix\Desktop\js-test\node_modules\xmlhttprequest\lib\XMLHttpRequest.js:447:13)
    at emitNone (events.js:91:20)
    at IncomingMessage.emit (events.js:185:7)
    at endReadableNT (_stream_readable.js:974:12)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)

Trying the same query in postman works just fine. The result body is:

[{"name":"default"}]

With following headers:
Cache-Control →no-cache
Content-Type →application/json
Date →Mon, 05 Mar 2018 18:09:49 GMT
Transfer-Encoding →chunked

Especially as the promise resolves successfully I don't see me doing anything wrong. But if so I'd be happy to see my error pointed out to me.

Best regards
Felix

Processing 'multipart/form-data' requests

Yet another problem with this rest client is how to handle 'multipart/form-data' requests (e.g. we want to send post with files).

I was able to make formData, but the header contentType goes without boundary, so the server cannot parse the request.

      rest.conf( {
        contentType: 'multipart/form-data',
        'multipart/form-data': {
          encode: params => {
            return Object.keys( params ).reduce( ( formData, paramName ) => {
              formData.append( paramName, params[ paramName ] )
              return formData
            }, new FormData() )
          }
        }
      } )

      let result = await rest[ UPLOAD ].post( params )

Any idea how to handle it?

Error running simple test

First of all I am not a javascript programmer.

I'm trying to make a simple call to a remote API on one of my servers. The code is below:

<html>
<head>
    <script src="rest-client.js"></script>
    <script>
        window.onload = function () {
            var api = new RestClient('https://api.asscontal.d.datanuvem.com/1.0');
            api.on('request', function(xhr) {
                xhr.setRequestHeader('Authorization', 'someapikey');
            });
            api.res({sites: 'sites'});

            api.sites.get().then(function(k){alert(k)});
        }
    </script>
</head>
<body>
</body>
</html>

However, the browser reports the following errors:

XMLHttpRequest cannot load https://api.asscontal.d.datanuvem.com/1.0/sites. Response for preflight is invalid (redirect)
rest-client.js:167 XHR failed loading: GET "https://api.asscontal.d.datanuvem.com/1.0/sites".
_request @ rest-client.js:167
self.get @ rest-client.js:236
window.onload @ daspanel.html:12
daspanel.html:1 Uncaught (in promise) XMLHttpRequest {readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, onreadystatechange: function…}

The copy of res-client.js I'm using was obtained from this URL: https://raw.githubusercontent.com/Amareis/another-rest-client/master/rest-client.js

What am I doing wrong ?

Thank you.

Renew token

How to renew JWT token?
To set authorization token I using:

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

, but what I should do when get a new token?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.