1.9.2 • Published 5 years ago

@ciklum/waas v1.9.2

Weekly downloads
-
License
ISC
Repository
gitlab
Last release
5 years ago

WAAS

Web App Api Service (WAAS) - lightweight library that organizes your APIs. It is based on the next principles:

  • event-based request handling
  • services with unlimited inheritance
  • adapters instead of concrete tools

Table of contents

  1. Installation and usage
  2. ApiService
  3. Events
  4. Working with local copy of module
  5. Publish a package

Installation and usage

Set private registry by adding registry=http://npm.pp.ciklum.com/ to your .npmrc file

Install WAAS library by running

npm i -S @ciklum/waas

Now you can use assets provided by WAAS:

import { AxiosHttpAdapter, ApiService, REQUEST_EVENTS } from '@ciklum/waas'

ApiService

ApiService class allows you to move the most of your payload outside your main code and care only about business logic.

HttpAdapters

Adapters tell ApiService how to work with some API. They should implement HttpAdapter interface to provide executeRequest and cancelRequest methods.

The default adapter, provided by the package, is AxiosHttpAdapter - an axios wrapper.

ApiService configuration

ApiService configuration should be one of type:

  • a service's URL string (parametrized URLs are allowed)
  • an object with two keys:
    • url - a mandatory key of type 'string' that represents API URL (parametrized URLs are allowed)
    • headers - an optional object with HTTP headers

ApiService creation

To create an ApiService you should provide HttpAdapter and configuration to the constructor:

import { AxiosHttpAdapter, ApiService } from '@ciklum/waas'

const axiosHttpAdapter = new AxiosHttpAdapter({
  baseURL: 'https://ciklum.com', // optional axios' configuration parameter
})
const config = {
  url: 'api',
  headers: {
    'content-type': 'application/json; charset=utf-8',
  },
}
const rootApiService = new ApiService(axiosHttpAdapter, config)

Service inheritance

ApiServices are extendable. You can create child ApiService, that extends parent's functionality using extend method.

For example, you can create a service for endpoint that needs authorization token:

// We use rootApiService created in the previous example

const authConfig = {
  headers: {
    Authorization: `Bearer ${getToken()}`,
  },
}
const protectedApiRoot = rootApiService.extend(authConfig)

So all requests sent with protectedApiRoot contain Content-Type (from rootApiService) and Authorization headers.

Then you can create a service to work with employees that needs authorization:

// We use protectedApiRoot created in the previous example

const employeeService = protectedApiRoot.extend('employee')

All requests sent with employeeService contain Content-Type (from rootApiService) and Authorization (from protectedApiRoot) headers. employeeService's URL is https://ciklum.com/api/employee.

ApiService's extend method concatenates URLs from configuration (from parent to children) and merges headers. If parent's header has the same name as child's header, child header's value should be set as the value of the header.

An argument provided to extend method should be

  • a string in case of changing URL only
  • an object with the same keys as for constructor, except all keys are optional.

Request API

ApiService provides request API:

  • get - send a GET request,
  • post - send a POST request,
  • put - send a PUT request,
  • patch - send a PATCH request,
  • delete - send a DELETE request.

Request configuration object could be provided to the method.

Configuration object with optional keys headers and params could be passed to the get and delete methods.
Configuration object with optional keys headers, params and data could be passed to the post, put and patch methods.

params represents an object, containing params for URL and query. For example:

// We use protectedApiRoot created in the previous example

const employeeConfig = {
  url: 'employee/:employeeId',
}

const employeeService = protectedApiRoot.extend(employeeConfig)

employeeService.get({
  params: {
    employeeId: 123,
    openedTab: 'general',
  }
})

// Request URL: https://ciklum.com/api/employee/123?openedTab=general

data field represents the data to be sent as the request body.

Additionally request could be cancelled by calling cancel method:

const employeeRequest = employeeService.get({ params: { employeeId: 123 } })
employeeRequest
  .on('cancel', () => { console.log('Request cancelled') })

employeeRequest.cancel()

Events

Event types

WAAS provides six types of custom events:

  • before - special type of event that used to modify request's configuration on-the-fly
  • request - event emitted before the request starts running
  • success - event emitted on successful response
  • error - event emitted on client errors (HTTP status code 4xx)
  • failure - event emitted on all errors except client errors (server errors, network errors etc.)
  • cancel - event emitted on cancelling the request
  • after - event emitted after all

You can use events by their names or import REQUEST_EVENTS constant:

import { REQUEST_EVENTS } from '@ciklum/waas'

// These two subscriptions to 'success' event are equivalent
apiService.get()
  .on('success', sucsessHandler)
  .on(REQUEST_EVENTS.success, sucsessHandler)

Events diagram

Events diagram

Event listeners

Method on provided by ApiService and request should be called to subscribe to the event.

Events request and cancel provide no payload to the listeners.

Events error and failure provide to the listeners HttpError that contains fields:

  • message
  • status
  • statusText.

Additionally these events provide function to retry request again.

employeeService.get({ params: { employeeId: 123 } })
  .on('error', (error, retry) => {
    if (error.status === 401) { // 401 Unauthorized
      updateToken() // update token
      return retry()
    }
    this.loggerService.error(error)
  })

Event success provides to the listeners an object of shape:

  • data - the response that was provided by the server
  • meta
    • status - the HTTP status code from the server response
    • statusText - the HTTP status message from the server response
    • url - the HTTP request URL
    • headers - the headers that the server responded with (all names are lower cased)
      • contentType

Special event 'before'

This special event lets you modify request configuration "on-the-fly". It provides and object with request configuration (payload key) and function to call the next before listener or to launch the HTTP request (next key).

For example if the authorization token is returned asynchronously:

import { ApiService, AxiosHttpAdapter, mergeConfig, REQUEST_EVENTS } from '@ciklum/waas'

const apiRoot = new ApiService(new AxiosHttpAdapter(), { url: '/api' })

const protectedApiRoot= apiRoot.extend()
  .on(REQUEST_EVENTS.before, ({ payload: prevConfig, next }) => {
    getToken()
      .then((token) => {
        const nextConfig = mergeConfig(
          prevConfig,
          {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          },
        )
        next(nextConfig)
      })
  })

Important: next should be called with transformed request configuration. This value will be passed to the next listener or to the adapter's executeRequest method. before events are emitted synchronously: from the root service to the last child and then to the request's listeners. HTTP request will be sent only after the last before listener executed.

Service event listeners

Services may have their own event listeners. It's just like default events but you apply it to services. All requests made with this service will have these events.

For example, we can implement logging:

import { ApiService, AxiosHttpAdapter,REQUEST_EVENTS } from '@ciklum/waas'

const apiRoot = new ApiService(new AxiosHttpAdapter(), { url: '/api' })

apiRoot
  .on(REQUEST_EVENTS.error, loggerService.error)
  .on(REQUEST_EVENTS.failure, loggerService.error)
  .on(REQUEST_EVENTS.success, loggerService.info)

So all requests made by apiRoot or by it's children will log error, failure and success events.

Request event listeners

Request listeners are similar to the Service event listeners but connected to the HTTP request only.

employeeService.get({ params: { employeeId }})
  .on('success', setEmployeeAction)
  .on('error', setEmployeeErrorAction)
  .on('failure', setEmployeeFailureAction)

Events sequence

Listeners could be registered to the same event for ApiServices and request. They are emitted from parent to child in order they were registered.

import  { ApiService, AxiosHttpAdapter } from '@ciklum/waas'

const rootService = new ApiService(new AxiosHttpAdapter(), { url: '/api' })
rootService
  .on('request', () => console.log('rootService::request:1'))
  .on('request', () => console.log('rootService::request:2'))

const protectedConfig = {
  headers: {
    Authorization: getToken(),
  },
}
const protectedService = rootService.extend(protectedConfig)
protectedService
  .on('request', () => console.log('protectedService::request:1'))
  .on('request', () => console.log('protectedService::request:2'))

const employeesConfig = {
  url: 'employees'
}
const employeesService = protectedService.extend(employeesConfig)
employeesService
  .on('request', () => console.log('employeesService::request:1'))
  .on('request', () => console.log('employeesService::request:2'))

employeesService.get()
  .on('request', () => console.log('employeesService.get::request:1'))
  .on('request', () => console.log('employeesService.get::request:2'))

The output for the example:

rootService::request:1
rootService::request:2

protectedService::request:1
protectedService::request:2

employeesService::request:1
employeesService::request:2

employeesService.get::request:1
employeesService.get::request:2

Working with local copy of module

When you want to develop new features for module, this section will be helpful for you. Package linking is a two-step process which solves this need. You need npm link for this.

Steps:

  • Create global link. It will be available in folder were your npm modules are.

    npm link

  • Links to the global installation target from your front end app.

    npm link @ciklum/waas

Publish a package

Publish a package to the registry by running

npm run build
npm publish
1.9.2

5 years ago

1.9.1

5 years ago

1.7.0

5 years ago

1.9.0

6 years ago