Fetch data on the server and client with an RPC style interface.
RPC is a natural way of expressing that a server-side function should be run in
response to a client-side function call. Unlike
RESTful architectures,
RPC-based architectures are not required to conform to statelessness constraints
and are free to return session-scoped data. Additionally, the semantics of RPC
calls are not constrained by the availability of suitably-descriptive HTTP
methods and RPC calls can express complex state change requests more naturally
as verbs (e.g. returnProduct(id)
) rather than object-orientation (e.g.
PATCH /api/orders/:id
).
If you're using React/Redux, you should use
fusion-plugin-rpc-redux-react
instead of this package.
yarn add fusion-plugin-rpc
import {createPlugin} from 'fusion-core';
export default createPlugin({
deps: {RPC: RPCToken},
middleware: ({RPCFactory}) => (ctx, next) => {
RPC.from(ctx).request('getUser', 1).then(console.log);
}
);
// src/main.js
import React from 'react';
import App, {createPlugin} from 'fusion-core';
import RPC, {
RPCToken,
RPCHandlersToken,
ResponseError,
} from 'fusion-plugin-rpc';
import UniversalEvents, {
UniversalEventsToken,
} from 'fusion-plugin-universal-events';
import {FetchToken} from 'fusion-tokens';
import fetch from 'unfetch';
// Define your rpc methods server side
const handlers = __NODE__ && {
getUser: async (args, ctx) => {
return {some: 'data' + args};
},
test: async (args, ctx) => {
// Error Handling Example
try {
doThing();
} catch (e) {
const error = new ResponseError('Failed to do thing');
error.code = 'DOTHING';
error.meta = {
custom: 'metadata',
};
throw error;
}
},
};
export default () => {
const app = new App(<div />);
app.register(RPCToken, RPC);
app.register(UniversalEventsToken, UniversalEvents);
__NODE__
? app.register(RPCHandlersToken, handlers)
: app.register(FetchToken, fetch);
return app;
};
The plugin can accept an optional config token for modifying the default behavior.
// src/main.js
import React from 'react';
import App, {createPlugin} from 'fusion-core';
import RPC, {
RPCToken,
RPCHandlersToken,
ResponseError,
RPCHandlersConfigToken,
} from 'fusion-plugin-rpc';
import UniversalEvents, {
UniversalEventsToken,
} from 'fusion-plugin-universal-events';
import {FetchToken} from 'fusion-tokens';
import fetch from 'unfetch';
import handlers from './redux/handlers';
export default () => {
const app = new App(<div />);
app.register(RPCHandlersConfigToken, {
// Modify RPC endpoints to be accessible at /nested/api/rpcs/<RPC_ID>
apiPath: 'nested/api/rpcs',
});
app.register(RPCToken, RPC);
app.register(UniversalEventsToken, UniversalEvents);
__NODE__
? app.register(RPCHandlersToken, handlers)
: app.register(FetchToken, fetch);
return app;
};
RPC
import RPC from 'fusion-plugin-rpc';
The RPC plugin. Provides the RPC service API.
RPCToken
import {RPCToken} from 'fusion-plugin-rpc-redux-react';
The canonical token for the RPC plugin. Typically, it should be registered with the RPC plugin.
UniversalEventsToken
Required. See https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-universal-events#api
RPCHandlersToken
import {RPCHandlersToken} from 'fusion-plugin-rpc';
Object with keys as the name of the handler and the value the handler implementation. Required. Server-only.
RPCHandlersConfigToken
import {RPCHandlersConfigToken} from 'fusion-plugin-rpc';
Configures what RPC handlers exist. Required. Server-only.
BodyParserOptionsToken
import {BodyParserOptionsToken} from 'fusion-plugin-rpc';
Configures options for koa-bodyparser
. Optional. See available options here.
For example, if you want to increase the limit for uploading large file sizes, set jsonLimit
to a higher limit:
app.register(BodyParserOptionsToken, {jsonLimit: '20mb'});
type RPCHandlers = Object<string, () => any>
You can register a value of type RPCHandlers
or a Plugin that provides a value
of type RPCHandlers
.
FetchToken
Required. Browser-only. See https://github.com/fusionjs/fusionjs/tree/master/fusion-tokens#fetchtoken
ReduxToken
Required. See https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-react-redux
ReducerToken
Required. See https://github.com/fusionjs/fusionjs/tree/master/fusion-plugin-react-redux
RPCHandlersConfigToken
Optional.
type RPCConfigType = {
apiPath?: string,
}
BodyParserOptionsToken
Optional. See koa-bodyparser Options type.
const rpc: RPC = Rpc.from((ctx: Context));
ctx: Context
- Required. A
Fusion.js context
returns rpc: {request: (method: string, args: any, headers: Object, options: RequestOptions) => Promise<any>}
request: (method: string, args: any) => Promise<any>
- Makes an RPC call
via an HTTP request. If on the server, this will directly call the method
handler with (args, ctx)
.If on the browser, this will POST
to /api/${method}
(unless modified;
see customization) endpoint with JSON serialized args as the
request body. The server will then deserialize the args and call the rpc
handler. The response will be serialized and send back to the browser.
method: string
- Required. The RPC method nameargs: any
- Optional. Arguments to pass to the server-side RPC handler.
Must be JSON-serializable or an instance of
FormData.headers: Object
- Optional. Browser only. HTTP headers to use when
making the request from the browser to the server.options: RequestOptions
- Optional. Browser only. Additional request
options to pass to the underlying fetch
call.The package also exports a mock RPC plugin which can be useful for testing. For example:
import {mock as MockRPC, RPCToken} from 'fusion-plugin-rpc';
app.register(RPCToken, mock);
Use the ResponseError
error subclass for sending error responses. If this
error class is not used, a generic message will be sent to the client.
import {ResponseError} from 'fusion-plugin-rpc';
function testHandler() {
try {
doThing();
} catch (e) {
const error = new ResponseError('Failed to do thing');
error.code = 'DOTHING';
error.meta = {
custom: 'metadata',
};
throw error;
}
}
The package also exports a getMockRpcHandlers util which can be useful for testing. Fixtures need to be of the following type
type RpcResponse = Object | ResponseError;
type RpcResponseMap = Array<{
args: Array<*>,
response: RpcResponse,
}>;
type RpcFixtureT = {[string]: RpcResponseMap | RpcResponse};
getMockRpcHandlers
has the following interface:
type getMockRpcHandlersT = (
fixtures: Array<RpcFixtureT>,
onMockRpc?: OnMockRpcCallbackT
) => HandlerType;
For example:
import {getMockRpcHandlers, ResponseError} from 'fusion-plugin-rpc';
const rpcFixtures = [
{
getUser: {
firstName: 'John',
lastName: 'Doe',
uuid: 123,
},
},
{
updateUser: [{
args: [{firstName: 'Jane'}],
response: {
firstName: 'John',
lastName: 'Doe',
uuid: 123,
},
}, {
args: [{firstName: ''}],
response: new ResponseError('Username cant be empty'),
}]
},
];
const mockRpcHandlers = getMockRpcHandlers(rpcFixtures);
const user = await mockRpcHandlers.getUser();
try {
const user = await mockRpcHandlers.updateUser({firstName: ''});
} catch (updatedUserError) {
// When error object is passed as response in fixtures,
// it will be considered as a failure scenario and will be thrown by rpc handler.
}