mion has an RPC style routing system so Routes are just functions that can be called remotely.
The first parameter of the remote method is always the Call Context. The rest of parameters are the remote parameters that get deserialized and validated before the route gets executed.
There are no URLs or Paths when defining a route, instead the API is defined using a plain JavaScript object, where every entry of the object is a route or middleFn. This way we simplify referencing remote methods when calling them from the client.
Internally a URL is generated for each route so these can be referenced using regular HTTP requests, but all this complexity is managed transparently by the client.
Routes can be defined using the route function by passing the Handler as first parameter and RouteOptions as second.
satisfies operator.import {Routes, route} from '@mionkit/router';
import {memoryStoreService as db} from './full-example.app.ts';
export const routes = {
sayHello: route((ctx, name1: string, name2: string): string => {
return `Hello ${name1} and ${name2}.`;
}),
getSomeData: route(async (ctx, id: string): Data | RpcError<'data-not-found'> => {
const data = await db.getData(id);
return data || new RpcError({publicMessage: 'Data not found', type: 'data-not-found'});
}),
} satisfies Routes;
We can register routes by passing the routes to initMionRouter or explicitly calling registerRoutes once router has been initialized.
import {HeadersSubset} from '@mionkit/core';
import {initMionRouter, Routes, CallContext, registerRoutes, route, headersFn} from '@mionkit/router';
export type Shared = () => Record<string, any>;
export type Context = CallContext<Shared>;
const authRoutes = {
logIn: route((c, email: string, password: string): string => 'loggedIn'),
logOut: route((): string => 'loggedOut'),
} satisfies Routes;
const routes = {
auth: headersFn((c: Context, h: HeadersSubset<'Authorization'>): void => undefined),
sayHello: route((c, name: string): string => 'hello' + name),
sayHello2: route((c, name: string): string => 'hello' + name),
} satisfies Routes;
export const mayApi = await initMionRouter(routes);
export const authApi = await registerRoutes(authRoutes);
// export api types to be consumed by the clients
export type MyApi = typeof mayApi;
export type AuthApi = typeof authApi;
Keep it simple and use regular valid JS variable names for routes. It is not recommended to use the array notation (using quotes) to define route names.
import {Routes, route} from '@mionkit/router';
const sayHello = route((ctx, name: string): string => {
return `Hello ${name}.`;
});
const routes = {
'say-Hello': sayHello, // path = /say-Hello !! NOT Recommended
'say Hello': sayHello, // path = /say%20Hello !! ROUTE WONT BE FOUND
} satisfies Routes;
The CallContext contains all the data related to the ongoing call.
Most of the data within the CallContext is marked as read-only, this is because it is not recommended to modify the context manually. It is still possible to modify it (the context is not a real Immutable JS object).
To share data between middleFns and routes use the shared object within the Call Context.
import {RpcError, HeadersSubset} from '@mionkit/core';
import {Routes, initMionRouter, headersFn, route} from '@mionkit/router';
import {getAuthUser, isAuthorized} from 'MyAuth';
const authorizationMiddleFn = headersFn(
async (context, {headers}: HeadersSubset<'Authorization', 'User-id'>): Promise<void | RpcError<'not-authorized'>> => {
const token = headers.Authorization;
const userId = headers['User-id'];
const me = await getAuthUser(token, userId);
if (!isAuthorized(me)) {
return new RpcError({publicMessage: 'user is not authorized', type: 'not-authorized'});
}
context.shared.myUser = me; // user is added to ctx to shared with other routes/middleFns
}
);
const sayMyName = route((context): string => {
return `hello ${context.shared.myUser.name}`;
});
const routes = {
authorizationMiddleFn,
sayMyName,
} satisfies Routes;
export const apiSpec = await initMionRouter(routes);
It is possible to define a contextDataFactory function used to initialize the context data object on every request.
This factory function will be called before any route or middleFn gets executed and the returned value will be the default shared object for all routes and middleFns.
import {initMionRouter, route} from '@mionkit/router';
import type {CallContext, Routes} from '@mionkit/router';
import type {User} from './full-example.app.ts';
interface ContextData {
myUser: User | null;
// ... other context data properties
}
const initContextData = (): ContextData => ({myUser: null});
type MyContext = CallContext<ContextData>;
const routes = {
getMyPet: route(async (ctx: MyContext): Promise<Pet> => {
const user = ctx.shared.myUser;
const pet = await myApp.db.getPetFromUser(user);
return pet;
}),
} satisfies Routes;
export const myApi = await initMionRouter(routes, {contextDataFactory: initContextData});