Bitlibs Documentation
  • Docs
  • Home
  • Help

›Modules

Getting started

  • Introduction
  • Usage
  • Modules overview

Tutorial

  • Forms

Modules

  • Datamodels
  • Datasets
  • Networking
  • Context
  • Loading
  • Pagination
  • Notifications
  • Storage
  • Utils

View layer integrations

  • Forms

Networking

Introduction

The networking module provides a modular, layered network stack for your application.

Use cases

The networking module can be used for, amongst other things:

  • Performing requests to remote services using a high-level API
  • Using different clients and/or service adapters for different remote services, or even different parts of a single remote service.
  • Encapsulating requests to perform into their own classes
  • Applying middleware to all or certain requests, responses and/or errors.

High-level overview

You perform network requests by supplying instances of Request subclasses to a Networker instance. The Networker looks at the supplied request and picks the most appropriate registered NetworkerClient for that request. You can register multiple clients at a networker to handle requests to different domains or even subpaths on those domains. A networker client has a service adapter which it uses to talk to the remote service at which it has been registered, converting (parts of) requests and responses when necessary.

You can also register middelware at a networker instance to apply to requests to certain domains or subpaths on those domains. Some middleware classes are provided by bitlibs, for example for logging purposes and including authentication credentials in the request.

The networker

The networker is the heart of the networking module. The way in which you use it is by first optionally registering one or more networker clients and/or middleware classes and then handing it Request instances to perform.

Registering clients

You register instances of NetworkClient subclasses for either all requests performed by that networker or only for requests to a certain domain or even subpath on a domain. The networker hands incoming requests to the most appropriate client to perform. If you do not register any specific client for a certain request, a generic NetworkClient instance will be used.

The networker determines the most appropriate client by picking the one registered closest to the request url. For example, for a request against url http://api.example.com/users/1/ it first looks for any client registered for subpath http://api.example.com/users/. If none is found it goes up a directory at a time until it finds a client registered for that path. If no domain-specific client is found at all it uses the globally registered client.

To register a client, use the registerClient method in one of the following ways:

import Networker from 'lib/bitlibs/network/Networker';
import NetworkerClient from 'lib/bitlibs/network/clients/NetworkerClient';
import JSONAPIClient from 'lib/bitlibs/network/clients/JSONAPIClient';

let networker = new Networker();

// Instantiate an API client and pass it the service root for which it will
// be used in order to be able to omit this root from request urls
let exampleRoot = "http://api.example.com/";
let exampleClient = new JSONAPIClient(exampleRoot);
networker.registerClient(exampleClient, exampleRoot);

// Register another client for requests to the user endpoints.
let usersRoot = `${exampleRoot}users/`;
let usersClient = new JSONAPIClient(usersRoot);
networker.registerClient(usersClient, usersRoot);

// Finally register a "catch-all" global networker client
let globalClient = new NetworkerClient();
networker.registerClient(globalClient);

Registering middleware

You can register middleware to be applied to either all requests or requests within a certain root. Middleware can be used to modify requests, responses and/or errors. The way in which the middleware to apply to a certain request is determined is similar to picking the networker client to use for a request, except for the fact that there can be more than one applicable middleware class for any given request. The middleware is performed in order of registration, meaning the one registered first for a certain path is also applied first.

Example:

import Networker from 'lib/bitlibs/network/Networker';
import LoggingMiddleware from 'lib/bitlibs/network/middleware/LoggingMiddleware';
import AuthenticationMiddleware from 'lib/bitlibs/network/clients/AuthenticationMiddleware';
import MyCustomMiddleware from 'src/network/middleware/MyCustomMiddleware';

let networker = new Networker();

// Register both the logging and authentication middlewares for the example domain.
let exampleRoot = "http://api.example.com/";
let loggingMiddleware = new LoggingMiddleware();
let authMiddleware = new AuthenticationMiddleware();
networker.registerMiddleware(loggingMiddleware, exampleRoot);
networker.registerMiddleware(authMiddleware, exampleRoot);

// In addition, register our own custom middleware to requests within the users endpoint root
let usersRoot = `${exampleRoot}users/`;
let customMiddleware = new MyCustomMiddleware(usersRoot);
networker.registerMiddleware(customMiddleware, usersRoot);

In this example, the following middleware is applied to requests to url http://api.example.com/users/1/, in this order:

  1. MyCustomMiddleware
  2. LoggingMiddleware
  3. AuthenticationMiddleware

Performing requests

Once you have your networker instance properly setup you can start performing requests with it. To this end simply call the perform method, passing it the request instance and any additional options to pass to all applied middleware classes:

import Networker from 'lib/bitlibs/network/Networker';
import APIEntityRequest from 'lib/bitlibs/network/requests/APIEntityRequest';

let networker = new Networker();
let usersRoot = "http://api.example.com/users/";
let getUserRequest = new APIEntityRequest(
  usersRoot,
  "get",
  {id: 3}
);

try {
  let response = await networker.perform(getUserRequest, {log: false});
} catch (error) {
  // Error is an instance of a subclass of NetworkerError
  console.log(error.getMessage());
}

In this example we use an APIEntityRequest to abstract performing requests regarding a certain entity to a RESTful API. We only have to pass it the method to perform, "retrieve", and the ID of the user to retrieve. We pass the log configuration property to the networker to signal to the logging middleware that this request should be exempted from logging. Performing this request using the networker results in the get method being called on the client which is chosen to handle the request, supplying it the final request url, parameters and headers.

Requests

A Request abstracts a certain type of request into its own class. When instantiating a request you pass it the url, the method to perform against that URL and optionally any request parameters, headers and extra options specific to that request class. When performing a request the networker calls the prepare method of the request, which can alter its attributes based on the given parameters. The APIEntityRequest for example uses this to build the final URL by appending the supplied ID of the entity to fetch to the given url. You could also use this to include a certain set of headers for example, such that you do not have to explicitly provide them everytime you execute this request (type).

See section Requests for additional information.

Responses

NetworkResponse (sub)classes provide a thin wrapper around native javascript Response objects. Service adapters can augment it with additional data like metadata. It provides several convenience methods for accessing certain parts of the response, including the native response instance itself.

See section Responses for additional information.

Handling errors

If a request yielded an error the networker client will raise an instance of a subclass of NetworkerError. This class encapsulates the error that occured, providing an error code and message and also including the request which caused the error and the response to that request, if any.

See section Errors for additional information.

Networker Clients

Networker clients are used by the networker to actually perform the requests for which they are eligible. You can instantiate a networker client for either a specific service root or for any requests in general. If you instantiate it with a certain service root as argument you are allowed to pass it relative urls for requests.

Performing requests

When performing a certain type of request method with a networker client, the instance method on that networker with the same name will be called to handle that request. Here method does not necessarily mean HTTP method: the APIRequest subclass also supports the more semantically named methods retrieve, list, create and update for example. So, when performing a request with method list, the list method on the NetworkerClient is called to handle it.

The logic for performing requests is partitioned according to the request method in this way because it corresponds to how typical RESTful APIs are structured.

All request methods call the fetch method of the base NetworkerClient class eventually. This method passes the request to the service adapter defined on the client for possible transformation into the format accepted by the service against which the request is being performed. It also passes the received response and any errors to the service adapter to transform.

To recap, the following steps are performed when executing requests using a networker:

  1. A Request is handed to the networker's perform method
  2. Any applicable middleware is applied to the request.
  3. The method on the most applicable NetworkerClient with the same name as the request's method is called.
  4. The client's dedicated request method calls the generic fetch method.
  5. The request is handed to the ServiceAdapter for possible transformation.
  6. The request is performed
  7. The response, or any error that occured, is handed to the ServiceAdapter for possible transformation.
  8. The response or error is returned to the networker.
  9. The networker applies any applicable middleware to the response/error.
  10. The networker returns the response/error to the caller.

Subclassing NetworkerClient

When subclassing NetworkerClient, here are the typical things to override/implement:

Supporting additional request methods

As stated, you support additional request methods by simply implementing instance methods with the same name on your subclass. This method is automatically called for every Request whose method attribute is equal to the instance method's name. It should accept the url, an optional dictionary of request parameters and an optional directory of headers as parameters.

getDefaultHeaders

This function determines the default set of headers to use for all requests performed by this networker client. These are overwritten by any explicitly provided headers.

getBaseRequestConfig

This method returns the base configuration to pass to the native javascript fetch method for every request performed by this client. The default implementation calls the getDefaultHeaders method for obtaining the default set of headers.

isErrorResponse

Should return whether a given response is regarded an error response. The default implementation regards all responses with status codes that do not start with the digit 2 as errors.

Requests

As explained in section Requests, a Request abstracts a certain type of request into its own class. Bitlibs provides several built-in request classes, mostly for easily performing requests against RESTful APIs.

APIRequest

The APIRequest class only adds functionality to append URLs with trailing slashes if so specified in the requests config.

APIEntityRequest

An APIEntityRequest encapsulates requests pertaining a particular instance of an entity on a RESTful API. You provide it with the root url of the endpoints regarding that entity type along with the ID of the entity to fetch, and the class will automatically build the final URL from those parameters.

APIEntityCollectionRequest

An APIEntityCollectionRequest encapsulates requests pertaining lists of a certain type of entity on a RESTful API. You provide it with the root url of the endpoints regarding that entity type along with any optional subpath relative to that root.

Responses

As stated in section Responses, a NetworkResponse provides a thin wrapper around the native javascript Response class. There is only one base NetworkResponse class provided by bitlibs, but service adapters can return instances of their own custom subclass with additional properties if required.

Metadata

The base NetworkResponse class contains a metadata field which is an instance of ResponseMetaData. This encapsulates the metadata returned by the service, if any, including convenience methods for obtaining this information.

Errors

Errors contain a uniquely identifiable error code, a message, the request which caused the error and optionally the response which signified this error. Built-in error codes are defined in file networking/errors/error_codes.js.

There are subclasses for every common HTTP error, some with their own additional fields.

Service Adapters

Service adapters translate requests and responses between the application and a certain (type of) service. By substituting one service adapter class for another you can easily interface with different services using the same application code. They hook into the functionality of the networker client on which they are defined at several different places:

Transforming request parameters

Service adapters are passed the set of request parameters before a request is performed. This can be used to alter the parameters in any way, for example by renaming certain parameters, adding new ones or removing some.

Processing raw responses

The service adapter is also responsible for returning an instance of a certain subclass of NetworkResponse for a given native Response object. Some adapters may provide their own subclass of NetworkResponse which contains additional data and/or functionality.

Processing deserialized responses

The last way in which service adapters can modify a response is by transforming a given NetworkResponse (subclass) instance. This method is only called for non-error responses and can be used to populate the response with additional information like metadata which is only included in non-error responses.

Middleware

Middleware processes and optionally transforms requests, responses and errors. It can also influence whether requests are performed at all, and suppress raised errors.

Processing requests

Requests are passed to middleware using the processRequest method. It receives the request along with a dictionary of options passed to the networker's perform method as arguments. It should return an array of two elements, being the (optionally modified) request and option dictionary, in that order. It may also raise an error, in which case the request is aborted.

Processing responses

The processResponse method receives the response instance to process along with the dictionary of options passed to the networker's perform method as arguments. It should return the response object, or raise an error to 'convert' the received response into an error.

Processing errors

Lastly middleware can process any raised errors using the processError method. This method receives the error and the options dictionary. Errors may be suppressed by not reraising them.

← DatasetsContext →
  • Introduction
    • Use cases
    • High-level overview
  • The networker
    • Registering clients
    • Registering middleware
    • Performing requests
    • Responses
    • Handling errors
  • Networker Clients
    • Performing requests
    • Subclassing NetworkerClient
  • Requests
    • APIRequest
    • APIEntityRequest
    • APIEntityCollectionRequest
  • Responses
    • Metadata
  • Errors
  • Service Adapters
    • Transforming request parameters
    • Processing raw responses
    • Processing deserialized responses
  • Middleware
    • Processing requests
    • Processing responses
    • Processing errors
Bitlibs Documentation
Docs
IntroductionDatamodelsDatasetsFormsNetworkingContextLoadingPaginationNotificationsStorageUtils
Bitlibs
HomeHelpAboutUser showcase
More
Brinkmann.IT
Copyright © 2018 Brinkmann.IT