@jackcom/app-network-layer
TypeScript icon, indicating that this package has built-in type declarations

2.0.0 • Public • Published

Application Network Request Layer

Change notes:
version 0.6.0 simplifies the library and may introduce some breaking changes. If you're looking for the old documentation look here.

version 2.x.x rewrites the underlying library and definitely introduces breaking changes. It also adds axios for more a stable api as well as predictable updates to the library.

What is it?

TLDR:

  1. Create something that looks like this:
const myEndpoints = {
  // POST request
  getUserById: {
    url: ({ id }) => `https://api.example.com/users/${id}`,
    method: "post"
  },
  
  // GET request
  listUsers: {
    url: () => `https://api.example.com/users`
  },
 
}
  1. Use the library's APIConfig export to turn it into this:
const api = APIConfig(myEndpoints);

// async/await
await api.listUsers();

// promise then/catch
api
  .getUserById({ id: ... })
  .then(user => ... )
  .catch(error =>  ... )

Manage your endpoints in a single place by turning a JS object-literal into an object for making REST requests.


Why does it?

This was created in an era before various tools (e.g. GraphQL) reduced the need for storing REST api endpoints in [mainly front-end] applications.

With that said, if you still reuse https://some-url in multiple files like this:

axios.get('https://some-url', someParams)

// or

fetch('https://some-url', someParams)

then this may be for you.


How do you use it?

Installation

npm i --save @jackcom/app-network-layer

Usage

Hint: you can create each of these object in separate files, and merge them wherever you create your APIConfig instance.
The following example is entirely mocked (i.e. not a real API that I know of), and is meant to convey the idea of using the APIConfig instance.

1: Define Your endpoints.

// Optional if you want to avoid case-sensitivity errors
import { METHODS } from "@jackcom/network-layer";

// Your 'endpoints' config object will be used by the APIConfig instance. 
// Name the keys intuitively, since they will be converted into method names on 
// the instance.
const endpointsConfig = {
  
  getUserById: {
    // URL is required on all objects. It must be a function (with any
    // logic and/or arguments) that returns a url string. 
    url: ({ id }) => `https://api.example.com/users/${id}`,
    method: METHODS.POST, // or "post"
  },
  
};
  

2: Create an instance of APIConfig.

This will return a singleton that consumes every outgoing request/response for your SPA.

import APIconfig from "./api-config";

const api = new APIConfig(endpoints);

// Now 'api' has methods that return Promises.
api
  .getUserById({ id: ... })
  .then(user => ... )
  .catch(error =>  ... )

// OR 

const response = await api.getUserById({ id: ... });

3: (Optional) Global error handling

You can supply an error-handler function to capture any failed api request. The handler must also return a Promise (e.g. rejected promise with custom error message; or a fallback success response).

3a. Define your global error handler
/**
 * Implementor (i.e. YOU)-defined global promise error handler
 * @param error Error wrapped in an object returned from the library
 * @param config Parameters used to make the failed request.
 * @param config.url Failed request endpoint
 */
function onGlobalError(
  error: { message: any }, 
  config: { url: string; config: RequestConfig }
) {
    // [ You can do any of the following in here
      
    // Call an external logging service with the error
    myExternalLoggerService.log(error)

    // and/or return a custom error so your program can continue
    return Promise.reject("NETWORK_RESPONSE_ERROR") 

    // or a custom success message so the caller never sees the error
    return Promise.resolve(myCustomSuccessMessage) 

    // You can even retry the failed request!
    return apiConfig.muhUsers()
}
3b. Supply endpoints AND the error-handler to your API config instance.

This is functionally similar to (2) above, except you're adding a new argument to the constructor. So we use the onGlobalError we defined above in (3a):

const api = new APIConfig(endpoints, onGlobalError);

try {
    // Now if the following request fails,
    const user = await api.getUserById({ id: badIdDoesNotExist });
}  catch (e) {
    // Note: if 'onGlobalError' doesn't return a rejected promise, 
    // any code in this 'catch' block will never run.
}

THAT'S IT!

If you also want the ability to handle all api errors in one place, read on.


API (available via import):

Note that although this guide uses NetworkLayer for clarity, the default export is not named.

  • NetworkLayer: default library export
  • NetworkLayer.APIConfig: the class to instantiate: route handler and promise-returner
  • NetworkLayer.METHODS: dictionary/key-value map of request methods

Terminology

RouteDefinition interface

A RouteDefinition defines a single resource endpoint. All properties are shown below:

intEndpointinition {
    acceptHeaders: string | undefined;
    contenEndpoint undefined;
    url: Function;
    authenticate: boolean | undefined;
    method: string | undefined
}

Explanation:

  • acceptHeaders: maps to headers["Accept"]; defaults to application/json
  • contentType: maps to headers["Content-Type"]; defaults to application/json;charset=utf-8
  • url: required function that takes params and returns a string. See example in How Do You Use It section above
  • authenticate: If used, this tells the ConfiguredRoute to check your params in request( ...).with(params) for a 'token' key to map to headers["Authorization"] (as "Bearer: {{ params.token }}").
  • method: maps to headers["method"]; defaults to GET

APIConfig class

This is the primary class you will instantiate with your routes object. You only need one instance, though you can instantiate as many as you wish.

Request Methods

The following methods can be specified (as members of the exported METHODS) when defining a route:

const METHODS = {
    POST: 'post',
    GET: 'get',
    DELETE: 'delete',
    PATCH: 'patch',
    PUT: 'put',
};

FAQs

Why would you use this Library?

This is ideal when you need to centralize the management of your application's routes. This system means that if server endpoints change, you only need to modify where your endpoints are defined, instead of in every view that may have been calling the server directly.

Why shouldn't you use this Library?

Because you know better Probably because you don't need to solve/have never encountered the issue it purports to solve.

Package Sidebar

Install

npm i @jackcom/app-network-layer

Weekly Downloads

0

Version

2.0.0

License

WTFPL

Unpacked Size

29.8 kB

Total Files

18

Last publish

Collaborators

  • jackcom