supercharged

Abstractions for building client-server interfaces for the web

Usage no npm install needed!

<script type="module">
  import supercharged from 'https://cdn.skypack.dev/supercharged';
</script>

README

supercharged

Supercharged is a set of abstractions for building robust client-server interfaces.

The library is split into three submodules:

  • client - Client-side (React) abstractions for exchanging data with the server
  • server - Interfaces and utilities for the server (Express)
  • shared - Constants and other primitive data common to both the client and server

Architecture

The abstractions provided by this library make a few assumptions:

  • Data is passed between the server and client as JSON over HTTP, or as strings over WebSocket
  • The client-side code uses React and higher-order components
  • The HTTP API exposed by the server is RESTful, exposing routes as resources with CRUD operations encapsulated in HTTP verbs

That said, this library provides:

  • Client input payload validation with ajv
  • Declarative interface for defining resources (routes) and their behaviors (HTTP verbs) on the server
  • Abstractions for bidirectional communication over WebSockets
  • Robust/fault-tolerant HTTP and WebSocket network call abstractions with HOCs on the client
  • Standardized interface for communicating error states between the server and client

Installation

# Primary dependency
$ npm install --save supercharged
# Peer dependencies
$ npm install --save @babel/core @babel/plugin-proposal-decorators express react

supercharged makes use of decorators in the server-side code. Add the following entry in .babelrc:

   "plugins": [
+    ["@babel/plugin-proposal-decorators", {"legacy": true}],
     ...
   ]

Usage

Server

Expose HTTP APIs and WebSocket tunnel endpoints on the server by defining routes with corresponding handlers:

import { HTTPHandler, WebSocketHandler, route, withSchema } from 'supercharged/server';

@route('/api/echo')
class EchoHTTPHandler extends HTTPHandler {
  @withSchema({
    properties: {
      echo: {
        type: 'string',
      },
    },
    required: ['echo'],
  })
  post({ echo }) {
    if (echo.length > 5) {
      return this.success({
        data: echo,
      });
    }

    return this.error({
      data: 'Echo text too short!',
    });
  }
}

@route('/ws/echo')
class EchoWebSocketHandler extends WebSocketHandler {
  onMessage(message) {
    this.sendMessage(message);
  }
}

Initialize your Express application with the defined handlers:

import Express from 'express';
import { supercharge } from 'supercharged/server';

const app = Express();

supercharge(app, [EchoHTTPHandler, EchoWebSocketHandler]);

app.listen(3000);

Client

Create a higher-order component that calls the server-defined HTTP API:

import React, { Component } from 'react';
import { withResource } from 'supercharged/client';

class EchoComponent extends Component {
  state = { text: '' };

  render() {
    const { echo: { invoke, data } } = this.props;
    const { text } = this.state;

    return (
      <div>
        <p>Most recent echo response: {data}</p>

        <input
          type="text"
          value={text}
          onChange={({ target: { value } }) => this.setState({ text: value })}
        />

        <button onClick={() => invoke({ echo: text }))}>
          Invoke echo endpoint
        </button>
      </div>
    );
  }
}

export default withResource({
  key: 'echo',
  method: 'POST',
  endpoint: '/api/echo',
  invokeOnMount: false,
})(EchoComponent);

For the WebSocket tunnel:

import React, { Component } from 'react';
import { withTunnel } from 'supercharged/client';

const EchoComponent = ({ echo: { sendMessage, messages } }) => (
  <div>
    <p>Messages sent from the server:</p>
    {messages.map((message) => <p>{message}</p>)}

    <button onClick={() => sendMessage('hello')}>Send message</button>
  </div>
);

export default withTunnel({
  key: 'echo',
  endpoint: '/ws/echo',
})(EchoComponent);