Client Overview
Routes are called like any other async function. The client only imports the API type from the server, so no server code ever reaches the browser.
Calling Routes
The routes and middlewares objects hold every remote route and middleware, except a middleware that takes no params and returns no data. Calling a route creates a RouteSubRequest, and call() sends it.
import {initClient} from '@mionjs/client';
// only the API type is imported from the server
import type {MyApi} from './hello.routes.ts';
const {routes} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
// call() never throws, it returns [result, error]
const [greeting, error] = await routes.sayHello('John').call();
if (error) console.log(error.publicMessage);
else console.log(greeting); // Hello John
import {createMionRouter, Routes} from '@mionjs/router';
const mion = createMionRouter();
const routes = {
sayHello: mion.route((ctx, name: string): string => `Hello ${name}`),
} satisfies Routes;
const myApi = mion.initRoutes(routes);
// the type the client uses, no server code ever reaches the browser
export type MyApi = typeof myApi;
The client follows the validation and JSON round trip rules set on the server, including the params sanitizing transforms. Wrong params fail in the browser, with no request sent.
Queries and Mutations
A query() is read only, so small payloads travel in the url as a GET. A mutation() changes data, so it always travels in the body as a POST. A plain route() also travels in the body.
import {initClient} from '@mionjs/client';
import type {MyApi} from './query-mutation.routes.ts';
const {routes} = initClient<MyApi>({
baseURL: 'http://localhost:3000',
});
// a GET, so it is cheap to cancel and easy to cache
const [user] = await routes.getUser('USER-123').call();
const [created] = await routes.createUser('Jane').call();
console.log(user?.name, created?.id);
import {createMionRouter, Routes} from '@mionjs/router';
const mion = createMionRouter();
export type User = {id: string; name: string};
const routes = {
getUser: mion.query((ctx, id: string): User => ({id, name: 'John'})),
createUser: mion.mutation(
(ctx, name: string): User => ({id: 'USER-123', name})
),
} satisfies Routes;
const myApi = mion.initRoutes(routes);
export type MyApi = typeof myApi;
Validating Server Responses
The server does not check its answers. Set validateServerResponses: true to check every answer and returned header against its type on the client.
The type errors of a wrong answer or header go in errorData.
import {initClient} from '@mionjs/client';
import type {MyApi} from './hello.routes.ts';
// off by default
const {routes} = initClient<MyApi>({
baseURL: 'http://localhost:3000',
validateServerResponses: true,
});
// a wrong answer is dropped and reported
const [greeting, error, response] = await routes.sayHello('John').call();
const [thrown] = response['@thrownErrors'] ?? [];
if (thrown?.type === 'response-validation-error') console.log(thrown.errorData);
else console.log(greeting, error);