Client
Modern client for mion APIs:
- Strongly typed APIs with autocompletion and static type checking.
- Fully typed list of remote methods with it's parameters and return values.
- Automattic Validation and Serialization out of the box.
- Local Validation (no need to make a server request to validate parameters)
- Prefill request data to persist across multiple calls.
End to End Type Safety
Using the client
To use mion client we just need to initialize the client using the RemoteApi
type returned from registerRoutes
.
It is important to just import the type so we don't import any of the actual routes or backend code into the client.
import {initClient} from '@mionkit/client';
// importing only the RemoteApi type from server
import type {MyApi} from './server.routes';
import {ParamsValidationResponse} from '@mionkit/reflection';
const {routes, hooks} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
Calling remote routes
The methods
object returned from initClient
contains all remote routes and hooks.
If a hook is not public (does not expect any parameters and can't return data) then it is not included within the methods
object.
Calling routes and hooks in the client generates a RouteSubRequest
or HookSubRequest
. This is so multiple HookSubRequest
can be added to a single RouteSubRequest
.
We use the call()
method from RouteSubRequest
to perform the remote call, you can pass any required HookSubRequest
data to this call method.
// calls sumTwo route in the server
const authSubRequest = hooks.auth('myToken-XYZ');
const sumTwoResp = await routes.utils.sum(5, 2).call(authSubRequest);
console.log(sumTwoResp); // 7
Prefilling Hooks data
For cases like authorization hooks that are required for all request, we can use the prefill()
method of HookSubRequest
. This will automatically persist the subRequest in local storage and prefill any future route calls that require that hook.
// prefills the token for any future requests, value is stored in localStorage
await hooks.auth('myToken-XYZ').prefill();
// // calls sumTwo route in the server
const sumTwoResponse = await routes.utils.sum(5, 2).call();
console.log(sumTwoResponse); // 7
Full example
import {initClient} from '@mionkit/client';
// importing only the RemoteApi type from server
import type {MyApi} from './server.routes';
const john = {id: '123', name: 'John', surname: 'Doe'};
const {routes, hooks} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
// prefills auth token for any future requests, value is stored in localStorage by default
await hooks.auth('myToken-XYZ').prefill();
// calls sayHello route in the server
const sayHello = await routes.users.sayHello(john).call();
console.log(sayHello); // Hello John Doe
// validate parameters locally without calling the server (await still required as validate is async)
const validationResp = await routes.users.sayHello(john).validate();
console.log(validationResp); // {hasErrors: false, totalErrors: 0, errors: []}
Type Reference
RemoteApi
This type could be considered your API schema to be used by the client. This is a Mapping of your public hooks and routes to PublicProcedure
export type RemoteApi<Type extends Routes> = // ... Maps Public Hooks and Routes to PublicProcedure
PublicProcedure
Required Metadata from Hook and Routes required for validation and serialization in the client.
export interface PublicProcedure<H extends Handler = any> {
type: ProcedureType;
/** Type reference to the route handler, it's runtime value is actually null, just used statically by typescript. */
handler: PublicHandler<H>;
/** Json serializable structure so the Type information can be transmitted over the wire */
serializedTypes: SerializedTypes;
id: string;
useValidation: boolean;
useSerialization: boolean;
params: string[];
hookIds?: string[];
pathPointers?: string[][];
headerName?: string;
}
SubRequest
export interface SubRequest<RM extends PublicProcedure> {
pointer: string[];
id: RM['id'];
isResolved: boolean;
params: Parameters<RM['handler']>;
return?: HandlerSuccessResponse<RM>;
error?: HandlerFailResponse<RM>;
validationResponse?: ParamsValidationResponse;
serializedParams?: any[];
// note this type can't contain functions, so it can be stored/restored from localStorage
}
RouteSubRequest
export interface RouteSubRequest<RR extends PublicRouteProcedure> extends SubRequest<RR> {
/**
* Validates Route's parameters. Throws RpcError if validation fails.
* @returns {hasErrors: false, totalErrors: 0, errors: []}
*/
validate: () => Promise<ParamsValidationResponse>;
/**
* Calls a remote route.
* Validates route and required hooks request parameters locally before calling the remote route.
* Throws RpcError if anything fails during the call (including validation or serialization) or if the remote route returns an error.
* @param hooks HookSubRequests requires by the route
* @returns
*/
call: <RHList extends HookSubRequest<any>[]>(...hooks: RHList) => Promise<HandlerSuccessResponse<RR>>;
}
HookSubRequest
export interface HookSubRequest<RH extends PublicHookProcedure | PublicHeaderProcedure> extends SubRequest<RH> {
/**
* Validates Hooks's parameters. Throws RpcError if validation fails.
* @returns {hasErrors: false, totalErrors: 0, errors: []}
*/
validate: () => Promise<ParamsValidationResponse>;
/**
* Prefills Hook's parameters for any future request. Parameters are also persisted in local storage for future requests.
* Validates and Serializes parameters before storing in local storage.
* Throws RpcError if validation or serialization fail or if the parameters can't be persisted.
* @returns Promise<void>
*/
prefill: () => Promise<void>;
/**
* Removes prefilled value.
* Throws RpcError if something fails removing the prefilled parameters
* @returns Promise<void>
*/
removePrefill: () => Promise<void>;
}