mion is the definitive TypeScript Framework for Full Stack APIs.
It offers The best Developer Experience for building Single Page Apps.
RPC architecture for simpler and easier to consume APIs.
Just use remote methods as any other local async method.
mion is focused on offering the best developer experience.
Fully validation adn serialization of params and results out of the box.
// packages/examples/src/_homepage/home-server.tsimport {function initMionRouter<R extends Routes>(routes: R, opts?: Partial<RouterOptions>): Promise<PublicApi<R>>initMionRouter, function route<H extends Handler>(handler: H, opts?: RouteOptions): RouteDef<H>route, Routes} from '@mionkit/router';import {function startNodeServer(options?: Partial<NodeHttpOptions>): Promise<Server | Server>startNodeServer} from '@mionkit/node';Automatic Validation and Serialization from Typescript typesinterface User { User.id: numberid: number; User.name: stringname: string; User.age: numberage: number; User.createdAt: DatecreatedAt: Date; User.tags: Set<string>tags: Set<string>;}interface Order { Order.id: stringid: string; Order.userId: numberuserId: number; Order.amount: numberamount: number }Object based router with rpc methods that receive Fully Validated paramsconst const routes: { getUser: RouteDef<(ctx: any, id: number) => User | null>; getOrder: RouteDef<(ctx: any, id: string) => Order | null>; sayHello: RouteDef<(ctx: any, name: string) => string>;}
routes = { getUser: RouteDef<(ctx: any, id: number) => User | null>getUser: route<(ctx: any, id: number) => User | null>(handler: (ctx: any, id: number) => User | null, opts?: RouteOptions): RouteDef<(ctx: any, id: number) => User | null>route((ctx: anyctx, id: numberid: number): User | null => { const const tags: Set<string>tags = new var Set: SetConstructornew <string>(iterable?: Iterable<string>) => Set<string> (+1 overload)
Set(['tag1', 'tag2']) const const user: Useruser: User = {User.id: numberid: 1234, User.name: stringname: 'John',User.age: numberage: 30, User.createdAt: DatecreatedAt: new var Date: DateConstructornew () => Date (+4 overloads)
Date(), User.tags: Set<string>tags}; if (id: numberid === 1234) return const user: Useruser; return null; }), getOrder: RouteDef<(ctx: any, id: string) => Order | null>getOrder: route<(ctx: any, id: string) => Order | null>(handler: (ctx: any, id: string) => Order | null, opts?: RouteOptions): RouteDef<(ctx: any, id: string) => Order | null>route((ctx: anyctx, id: stringid: string): Order | null => { const const order: Orderorder: Order = {Order.id: stringid: 'ORDER-123', Order.userId: numberuserId: 1234, Order.amount: numberamount: 100}; if (id: stringid === 'ORDER-123') return const order: Orderorder; return null; }), sayHello: RouteDef<(ctx: any, name: string) => string>sayHello: route<(ctx: any, name: string) => string>(handler: (ctx: any, name: string) => string, opts?: RouteOptions): RouteDef<(ctx: any, name: string) => string>route((ctx: anyctx, name: stringname: string): string => `Hello ${name: stringname}`),} satisfies Routes;export const const myApi: { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
myApi = await initMionRouter<{ getUser: RouteDef<(ctx: any, id: number) => User | null>; getOrder: RouteDef<(ctx: any, id: string) => Order | null>; sayHello: RouteDef<(ctx: any, name: string) => string>;}>(routes: { getUser: RouteDef<(ctx: any, id: number) => User | null>; getOrder: RouteDef<(ctx: any, id: string) => Order | null>; sayHello: RouteDef<(ctx: any, name: string) => string>;}, opts?: Partial<RouterOptions>): Promise<{ getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<...>; sayHello: PublicRoute<...>;}>
initMionRouter(const routes: { getUser: RouteDef<(ctx: any, id: number) => User | null>; getOrder: RouteDef<(ctx: any, id: string) => Order | null>; sayHello: RouteDef<(ctx: any, name: string) => string>;}
routes);export type type MyApi = { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
MyApi = typeof const myApi: { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
myApi;function startNodeServer(options?: Partial<NodeHttpOptions>): Promise<Server | Server>startNodeServer({port?: numberport: 3000});Fully typed client that seamlessly bridges frontend and backend with static type checking, autocompletion, automatic validation and serialization.
Lightweight and framework-agnostic — use it with React, Vue, Svelte, or any frontend framework.
// packages/examples/src/_homepage/home-client.tsimport {function initClient<RM extends RemoteApi>(options: InitOptions): { client: MionClient; routes: ClientRoutes<RM>; middleFns: ClientMiddleFns<RM>;}
initClient} from '@mionkit/client';import type {type MyApi = { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
MyApi} from './home-server.ts';const {const routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<(name: string) => Promise<string>>;}
routes} = initClient<{ getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}>(options: InitOptions): { client: MionClient; routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<...>; }; middleFns: {};}
initClient<type MyApi = { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
MyApi>({ baseURL: stringbaseURL: 'http://localhost:3000',});Autocomplete: shows available routesconst [const user: Useruser, const error: ValidationErrorerror] = await const routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<(name: string) => Promise<string>>;}
routes.- getOrder
- getUser
- sayHello
getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>getUser(1234).RSubRequest<(id: number) => Promise<User>>.call: () => Promise<Result<User, ValidationError, Record<string, unknown>, Record<string, ValidationError | RpcError<string, unknown>>>>Calls a remote route and returns a Result 4-tuple with full typing preservedcall();if (const user: Useruser) { const user: Useruser.User.createdAt: DatecreatedAt;Native Classes are automatically restored to their original types const user: Useruser.User.tags: Set<string>tags;}// Type error: id must be a numberconst routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<(name: string) => Promise<string>>;}
routes.getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>getUser('1234').RSubRequest<(id: number) => Promise<User>>.call: () => Promise<Result<User, ValidationError, Record<string, unknown>, Record<string, ValidationError | RpcError<string, unknown>>>>Calls a remote route and returns a Result 4-tuple with full typing preservedcall();Execute multiple routes in a single HTTP request. Batch API calls together, and Orchestrate router logic from the client.
mapFrom.// packages/examples/src/_homepage/home-mapFrom.tsimport {function initClient<RM extends RemoteApi>(options: InitOptions): { client: MionClient; routes: ClientRoutes<RM>; middleFns: ClientMiddleFns<RM>;}
initClient, function routesFlow<Routes extends RSubRequest<any>[], MiddleFns extends Record<string, HSubRequest<any>> = Record<string, never>>(routeSubRequests: [...Routes], middleFns?: MiddleFns): Promise<WorkflowResult<Routes, MiddleFns>>Creates and executes a routesFlow request with multiple routesroutesFlow, function mapFrom<FromSR extends SubRequest<any>, MappedInput>(source: FromSR, mapper: (value: FromSR["resolvedValue"]) => MappedInput, bodyHash?: string): MapFromServerFnRef<(value: FromSR["resolvedValue"]) => MappedInput>Maps the output of one route SubRequest to the input of another within a routesFlow.
The mapper function must be pure (same rules as pureServerFn).
The bodyHash is injected at build time by the mion vite plugin.mapFrom} from '@mionkit/client';import type {type MyApi = { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
MyApi} from './home-server.ts';const {const routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<(name: string) => Promise<string>>;}
routes} = initClient<{ getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}>(options: InitOptions): { client: MionClient; routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<...>; }; middleFns: {};}
initClient<type MyApi = { getUser: PublicRoute<(id: number) => Promise<User>>; getOrder: PublicRoute<(id: string) => Promise<Order>>; sayHello: PublicRoute<(name: string) => Promise<string>>;}
MyApi>({baseURL: stringbaseURL: 'http://localhost:3000'});const const orderReq: RSubRequest<(id: string) => Promise<Order>>orderReq = const routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<(name: string) => Promise<string>>;}
routes.getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>getOrder('ORDER-123');mapFrom order.userId → getUser input, (mapping function runs server-side)const const userIdMapping: numberuserIdMapping = mapFrom<RSubRequest<(id: string) => Promise<Order>>, number>(source: RSubRequest<(id: string) => Promise<Order>>, mapper: (value: Order) => number, bodyHash?: string): MapFromServerFnRef<(value: Order) => number>Maps the output of one route SubRequest to the input of another within a routesFlow.
The mapper function must be pure (same rules as pureServerFn).
The bodyHash is injected at build time by the mion vite plugin.mapFrom(const orderReq: RSubRequest<(id: string) => Promise<Order>>orderReq, (order: Orderorder) => order: Orderorder!.Order.userId: numberuserId).MapFromServerFnRef<(value: Order) => number>.type(): numberReturns this reference cast as ReturnType<F>, allowing it to be passed as a parameter to subrequeststype();const const userReq: RSubRequest<(id: number) => Promise<User>>userReq = const routes: { getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RSubRequest<(name: string) => Promise<string>>;}
routes.getUser: (id: number) => RSubRequest<(id: number) => Promise<User>>getUser(const userIdMapping: numberuserIdMapping);const [[const order: Orderorder, const user: Useruser]] = await routesFlow<[RSubRequest<(id: string) => Promise<Order>>, RSubRequest<(id: number) => Promise<User>>], Record<string, never>>(routeSubRequests: [RSubRequest<(id: string) => Promise<Order>>, RSubRequest<(id: number) => Promise<User>>], middleFns?: Record<string, never>): Promise<WorkflowResult<[RSubRequest<(id: string) => Promise<Order>>, RSubRequest<(id: number) => Promise<User>>], Record<...>>>Creates and executes a routesFlow request with multiple routesroutesFlow([const orderReq: RSubRequest<(id: string) => Promise<Order>>orderReq, const userReq: RSubRequest<(id: number) => Promise<User>>userReq]);if (const order: Orderorder && const user: Useruser) { console.log(`Order ${const order: Orderorder.Order.id: stringid} placed by ${const user: Useruser.User.name: stringname}`);}mion use RunTypes behinds the scene to generate JIT-compiled validation and serialization functions directly from TypeScript types. RunTypes supports advanced type formats and can be used as a standalone library.
No schemas libraries needed — Typescript is the single source of truth.
// packages/examples/src/_homepage/home-run-types.tsimport {function createIsTypeFn<T>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<IsTypeFn>Returns a function that checks if the given value is of the specified type.createIsTypeFn, function createStringifyJsonFn<T>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<JsonStringifyFn>Returns a function that stringifies a javascript value to a json string.
Stringifies special types like dates, bigints, maps, set, etc...
Is equivalent to calling prepareForJson and then json.stringify but more efficient.createStringifyJsonFn, function createMockTypeFn<T>(type?: ReceiveType<T>): Promise<(opts?: Partial<RunTypeOptions>) => T>Returns a function that mocks a value of the specified type.createMockTypeFn, function createToBinaryFn<T>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<ToBinaryFn>Returns a function that serializes any type value to a binary format.createToBinaryFn} from '@mionkit/run-types';interface User { User.id: stringid: string; User.name: stringname: string; User.createdAt: DatecreatedAt: Date; User.tags: Set<string>tags: Set<string>;}Create JIT-compiled functions directly from TypeScript typesconst const isUser: IsTypeFnisUser = await createIsTypeFn<User>(opts?: RunTypeOptions, type?: ReceiveType<User>): Promise<IsTypeFn>Returns a function that checks if the given value is of the specified type.createIsTypeFn<User>();const const stringify: JsonStringifyFnstringify = await createStringifyJsonFn<User>(opts?: RunTypeOptions, type?: ReceiveType<User>): Promise<JsonStringifyFn>Returns a function that stringifies a javascript value to a json string.
Stringifies special types like dates, bigints, maps, set, etc...
Is equivalent to calling prepareForJson and then json.stringify but more efficient.createStringifyJsonFn<User>();const const toBinary: ToBinaryFntoBinary = await createToBinaryFn<User>(opts?: RunTypeOptions, type?: ReceiveType<User>): Promise<ToBinaryFn>Returns a function that serializes any type value to a binary format.createToBinaryFn<User>();const const mockUser: (opts?: Partial<RunTypeOptions>) => UsermockUser = await createMockTypeFn<User>(type?: ReceiveType<User>): Promise<(opts?: Partial<RunTypeOptions>) => User>Returns a function that mocks a value of the specified type.createMockTypeFn<User>();Generate mock data - respects type structureconst const user: Useruser = const mockUser: (opts?: Partial<RunTypeOptions>) => UsermockUser();Validate data at runtimeconst isUser: (value: any) => booleanisUser(const user: Useruser);Serialize complex types (Date, Set, unions) to JSONconst const json: stringjson = const stringify: (value: any) => JSONStringstringify(const user: Useruser);Auto-generate Drizzle ORM table schemas directly from types using reflection.
Simply extends your types with SQL/Drizzle specific configuration.
Keep DB and Validation/Serialization logic separated.
// packages/examples/src/_homepage/home-drizzle.tsimport {function mapPGTable<T>(config?: DrizzleMapperConfig, type?: ReceiveType<T>): { build<TN extends string, TConfig extends PgTableConfig<T>>(tableName: TN, tableConfig?: TConfig): PgTableWithColumns<{ name: TN; schema: undefined; columns: BuildColumns<TN, MergedPgColumns<...>, "pg">; dialect: "pg"; }>;}
Creates a PostgreSQL table schema from a TypeScript type.
Auto-generates drizzle column definitions based on the type's properties.
Use .build(tableName, config?) to create the table with optional column overrides.mapPGTable} from '@mionkit/drizzle';import {function uuid(): PgUUIDBuilderInitial<""> (+1 overload)uuid, function text(): PgTextBuilderInitial<"", [string, ...string[]]> (+2 overloads)text, function timestamp(): PgTimestampBuilderInitial<""> (+2 overloads)timestamp} from 'drizzle-orm/pg-core';// Note: Must use regular import (not `import type`) for reflection to workimport {type StrUUIDv7 = string & { brand: "uuid";}
UUID v7 format, always branded with 'uuid'.StrUUIDv7, type StrEmail<EP extends FormatParams_Email = DEFAULT_EMAIL_PARAMS<RegExp, EMAIL_SAMPLES>> = string & { brand: "email";}
Email format, always branded with 'email'.StrEmail} from '@mionkit/type-formats/FormatsString';Define Models using type-formats for validation and serialization functionalityinterface User { User.id: string & { brand: "uuid";}
id: type StrUUIDv7 = string & { brand: "uuid";}
UUID v7 format, always branded with 'uuid'.StrUUIDv7; User.email: string & { brand: "email";}
email: type StrEmail<EP extends FormatParams_Email = DEFAULT_EMAIL_PARAMS<RegExp, EMAIL_SAMPLES>> = string & { brand: "email";}
Email format, always branded with 'email'.StrEmail; User.name: stringname: string; User.bio?: stringbio?: string; User.age: numberage: number; User.createdAt: DatecreatedAt: Date;}Auto-generate Drizzle table cond configure keys, indexes, etc..const const users: PgTableWithColumns<{ name: "users"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "users"; dataType: "string"; columnType: "PgUUID"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; email: PgColumn<{ name: "email"; tableName: "users"; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [...]; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; name: PgColumn<...>; bio?: PgColumn<...>; age: PgColumn<...>; createdAt: PgColumn<...>; }; dialect: "pg";}>
users = mapPGTable<User>(config?: DrizzleMapperConfig, type?: ReceiveType<User>): { build<TN, TConfig>(tableName: TN, tableConfig?: TConfig): PgTableWithColumns<{ name: TN; schema: undefined; columns: { id: PgColumn<{ name: (Omit<("id" extends keyof TConfig ? TConfig[keyof TConfig & "id"] extends PgColumnBuilderBase<ColumnBuilderBaseConfig<ColumnDataType, string>, object> ? TConfig[keyof TConfig & "id"] : PgColumnType<...> : PgUUIDBuilderInitial<...>)["_"], "name"> & { ...; })["name"]; ... 13 more ...; generated: Omit<...> & { ...; } extends { ...; } ? unknown extends G ? undefined : G extends undefined ? undefined : G : undefined; }, {}, { [K in keyof Omit<...>]: Omit<...>[K]; }>; ... 4 more ...; createdAt: PgColumn<...>; }; dialect: "pg"; }>;}
Creates a PostgreSQL table schema from a TypeScript type.
Auto-generates drizzle column definitions based on the type's properties.
Use .build(tableName, config?) to create the table with optional column overrides.mapPGTable<User>().build<"users", { id: IsPrimaryKey<NotNull<PgUUIDBuilderInitial<"id">>>; email: NotNull<PgTextBuilderInitial<"email", [string, ...string[]]>>;}>(tableName: "users", tableConfig?: { id: IsPrimaryKey<NotNull<PgUUIDBuilderInitial<"id">>>; email: NotNull<PgTextBuilderInitial<"email", [string, ...string[]]>>;}): PgTableWithColumns<{ name: "users"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "users"; dataType: "string"; ... 11 more ...; generated: undefined; }, {}, {}>; ... 4 more ...; createdAt: PgColumn<...>; }; dialect: "pg";}>
build('users', { id: IsPrimaryKey<NotNull<PgUUIDBuilderInitial<"id">>>id: uuid<"id">(name: "id"): PgUUIDBuilderInitial<"id"> (+1 overload)uuid('id').ColumnBuilder<{ name: "id"; dataType: "string"; columnType: "PgUUID"; data: string; driverParam: string; enumValues: undefined; }, object, object & { dialect: "pg"; }, ColumnBuilderExtraConfig>.primaryKey(): IsPrimaryKey<NotNull<PgUUIDBuilderInitial<"id">>>Adds a `primary key` clause to the column definition. This implicitly makes the column `not null`.
In SQLite, `integer primary key` implicitly makes the column auto-incrementing.primaryKey(), email: NotNull<PgTextBuilderInitial<"email", [string, ...string[]]>>email: text<"email", string, readonly [string, ...string[]]>(name: "email", config?: PgTextConfig<[string, ...string[]] | readonly [string, ...string[]]>): PgTextBuilderInitial<"email", [string, ...string[]]> (+2 overloads)text('email').ColumnBuilder<{ name: "email"; dataType: "string"; columnType: "PgText"; data: string; enumValues: [string, ...string[]]; driverParam: string; }, { enumValues: [string, ...string[]]; }, object & { dialect: "pg"; }, ColumnBuilderExtraConfig>.notNull(): NotNull<PgTextBuilderInitial<"email", [string, ...string[]]>>Adds a `not null` clause to the column definition.
Affects the `select` model of the table - columns *without* `not null` will be nullable on select.notNull().PgColumnBuilder<{ name: "email"; dataType: "string"; columnType: "PgText"; data: string; enumValues: [string, ...string[]]; driverParam: string; }, { enumValues: [string, ...string[]]; }, object, ColumnBuilderExtraConfig>.unique(name?: string, config?: { nulls: "distinct" | "not distinct";}): NotNull<PgTextBuilderInitial<"email", [string, ...string[]]>>
unique(),});The table schema is fully typed - columns match your interfaceconst users: PgTableWithColumns<{ name: "users"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "users"; dataType: "string"; columnType: "PgUUID"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; email: PgColumn<{ name: "email"; tableName: "users"; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [...]; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; name: PgColumn<...>; bio?: PgColumn<...>; age: PgColumn<...>; createdAt: PgColumn<...>; }; dialect: "pg";}>
users.id: PgColumn<{ name: "id"; tableName: "users"; dataType: "string"; columnType: "PgUUID"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; identity: undefined; generated: undefined;}, {}, {}>
id;Our binary protocol is designed to support al Typescript features: unions, optional props, rest params, circular types and any type you can think about!
Achieve smaller payloads and faster data transfer with automatic binary serialization for Dates, BigInts, Maps, Sets, and complex nested types.
Run mion APIs in Node.js, Bun or Serverless platforms like Aws Lambda and Google cloud functions.