Typescript Full Stack APIsAt The Speed Of Light ⚡
mion is the definitive TypeScript Framework for Full Stack APIs.
It offers The best Developer Experience for building Single Page Apps.
Mion Features
RPC Like
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 query<H extends Handler>(handler: H, opts?: RouteOptions): RouteDef<H>query, function route<H extends Handler>(handler: H, opts?: RouteOptions): RouteDef<H>route, Routes} from '@mionjs/router';import {function startNodeServer(options?: Partial<NodeHttpOptions>): Promise<HttpServer | HttpsServer>startNodeServer} from '@mionjs/platform-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: query<(ctx: any, id: number) => User | null>(handler: (ctx: any, id: number) => User | null, opts?: RouteOptions): RouteDef<(ctx: any, id: number) => User | null>query((ctx: anyctx, id: numberid: number): User | null => { if (id: numberid !== 1234) return 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}; return const user: Useruser; }), getOrder: RouteDef<(ctx: any, id: string) => Order | null>getOrder: query<(ctx: any, id: string) => Order | null>(handler: (ctx: any, id: string) => Order | null, opts?: RouteOptions): RouteDef<(ctx: any, id: string) => Order | null>query((ctx: anyctx, id: stringid: string): Order | null => { if (id: stringid !== 'ORDER-123') return null; const const order: Orderorder: Order = {Order.id: stringid: 'ORDER-123', Order.userId: numberuserId: 1234, Order.amount: numberamount: 100}; return const order: Orderorder; }), 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<HttpServer | HttpsServer>startNodeServer({port?: numberport: 3000});Fully Typed Client
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: InitClientOptions): { client: MionClient; routes: ClientRoutes<RM>; middleFns: ClientMiddleFns<RM>;}
initClient} from '@mionjs/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) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<(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: InitClientOptions): { client: MionClient; routes: { getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<...>; }; 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) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<(name: string) => Promise<string>>;}
routes.- getOrder
- getUser
- sayHello
getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>getUser(1234).RouteSubRequest<(id: number) => Promise<User>>.call(setup?: { middleFns?: never; otherRoutes?: never; signal?: AbortSignal; timeout?: number;}): Promise<Result<User, ValidationError, Record<string, unknown>, Record<string, RpcError<string, unknown>>>> (+2 overloads)
call();if (const user: Useruser) { const user: Useruser.User.createdAt: DatecreatedAt;Native Classes Like Set are automatically serialized/deserialized const user: Useruser.User.tags: Set<string>tags;}// Type error: id must be a numberconst routes: { getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<(name: string) => Promise<string>>;}
routes.getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>getUser('1234').RouteSubRequest<(id: number) => Promise<User>>.call(setup?: { middleFns?: never; otherRoutes?: never; signal?: AbortSignal; timeout?: number;}): Promise<Result<User, ValidationError, Record<string, unknown>, Record<string, RpcError<string, unknown>>>> (+2 overloads)
call();Routes Flows
Execute multiple routes in a single HTTP request. Batch API calls together, and Orchestrate router logic from the client.
serverMapFrom.// packages/examples/src/_homepage/home-mapFrom.tsimport {function initClient<RM extends RemoteApi>(options: InitClientOptions): { client: MionClient; routes: ClientRoutes<RM>; middleFns: ClientMiddleFns<RM>;}
initClient, function routesFlow<Routes extends RouteSubRequest<any>[]>(routeSubRequests: [...Routes]): RoutesFlowBuilder<Routes>routesFlow, function serverMapFrom<FromSR extends SubRequest<any>, MappedInput>(source: FromSR, mapper: (value: FromSR["resolvedValue"]) => MappedInput, bodyHash?: string): MapFromServerFnRef<(value: FromSR["resolvedValue"]) => MappedInput>serverMapFrom} from '@mionjs/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) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<(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: InitClientOptions): { client: MionClient; routes: { getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<...>; }; 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: RouteSubRequest<(id: string) => Promise<Order>>orderReq = const routes: { getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<(name: string) => Promise<string>>;}
routes.getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>getOrder('ORDER-123');serverMapFrom order.userId → getUser input, (mapping function runs server-side)const const userIdMapping: MapFromServerFnRef<(value: Order) => number>userIdMapping = serverMapFrom<RouteSubRequest<(id: string) => Promise<Order>>, number>(source: RouteSubRequest<(id: string) => Promise<Order>>, mapper: (value: Order) => number, bodyHash?: string): MapFromServerFnRef<(value: Order) => number>serverMapFrom(const orderReq: RouteSubRequest<(id: string) => Promise<Order>>orderReq, (order: Orderorder) => order: Orderorder!.Order.userId: numberuserId);const const userReq: RouteSubRequest<(id: number) => Promise<User>>userReq = const routes: { getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>; getOrder: (id: string) => RouteSubRequest<(id: string) => Promise<Order>>; sayHello: (name: string) => RouteSubRequest<(name: string) => Promise<string>>;}
routes.getUser: (id: number) => RouteSubRequest<(id: number) => Promise<User>>getUser(const userIdMapping: MapFromServerFnRef<(value: Order) => number>userIdMapping.MapFromServerFnRef<(value: Order) => number>.asArg(): numberasArg());const [[const order: Orderorder, const user: Useruser]] = await routesFlow<[RouteSubRequest<(id: string) => Promise<Order>>, RouteSubRequest<(id: number) => Promise<User>>]>(routeSubRequests: [RouteSubRequest<(id: string) => Promise<Order>>, RouteSubRequest<(id: number) => Promise<User>>]): RoutesFlowBuilder<[RouteSubRequest<(id: string) => Promise<Order>>, RouteSubRequest<(id: number) => Promise<User>>]>routesFlow([const orderReq: RouteSubRequest<(id: string) => Promise<Order>>orderReq, const userReq: RouteSubRequest<(id: number) => Promise<User>>userReq]).RoutesFlowBuilder<[RouteSubRequest<(id: string) => Promise<Order>>, RouteSubRequest<(id: number) => Promise<User>>]>.call(setup?: { middleFns?: never; signal?: AbortSignal; timeout?: number;}): Promise<WorkflowResult<[RouteSubRequest<(id: string) => Promise<Order>>, RouteSubRequest<(id: number) => Promise<User>>], Record<string, MiddlewareSubRequest<any>>>> (+1 overload)
call();if (const order: Orderorder && const user: Useruser) { console.log(`Order ${const order: Orderorder.Order.id: stringid} placed by ${const user: Useruser.User.name: stringname}`);}RunTypes ©
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>createIsTypeFn, function createStringifyJsonFn<T>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<JsonStringifyFn>createStringifyJsonFn, function createMockTypeFn<T>(type?: ReceiveType<T>): Promise<(opts?: Partial<RunTypeOptions>) => T>createMockTypeFn, function createToBinaryFn<T>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<ToBinaryFn>createToBinaryFn} from '@mionjs/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<T>): Promise<IsTypeFn>createIsTypeFn<User>();const const stringifyUser: JsonStringifyFnstringifyUser = await createStringifyJsonFn<User>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<JsonStringifyFn>createStringifyJsonFn<User>();const const toBinaryUser: ToBinaryFntoBinaryUser = await createToBinaryFn<User>(opts?: RunTypeOptions, type?: ReceiveType<T>): Promise<ToBinaryFn>createToBinaryFn<User>();const const mockUser: (opts?: Partial<RunTypeOptions>) => UsermockUser = await createMockTypeFn<User>(type?: ReceiveType<T>): Promise<(opts?: Partial<RunTypeOptions>) => User>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 stringifyUser: (value: any) => JSONStringstringifyUser(const user: Useruser);Drizzle ORM
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 toDrizzlePGTable<T, TN extends string = string, TConfig extends PgTableConfig<T> = {}>(tableName: TN, tableConfig?: TConfig, mapperConfig?: DrizzleMapperConfig, type?: ReceiveType<T>): PgTableWithColumns<{ name: TN; schema: undefined; columns: BuildColumns<TN, MergedPgColumns<T, TConfig>, "pg">; dialect: "pg";}>
toDrizzlePGTable} from '@mionjs/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 FormatUUIDv7 = anyFormatUUIDv7, type FormatEmail<EP extends FormatParams_Email = DEFAULT_EMAIL_PARAMS<RegExp, EMAIL_SAMPLES>> = anyFormatEmail} from '@mionjs/type-formats/StringFormats';Define Models using type-formats for validation and serialization functionalityinterface User { User.id: anyid: type FormatUUIDv7 = anyFormatUUIDv7; User.email: anyemail: type FormatEmail<EP extends FormatParams_Email = DEFAULT_EMAIL_PARAMS<RegExp, EMAIL_SAMPLES>> = anyFormatEmail; 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: string; schema: undefined; columns: { id: FormatUUIDv7; email: FormatEmail; name: PgColumn<{ name: "name"; tableName: string; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; bio: PgColumn<{ name: "bio"; tableName: string; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: false; hasDefault: false; isPrimaryKey: false; ... 5 more ...; generated: undefined; }, {}, {}>; age: PgColumn<...>; createdAt: PgColumn<...>; }; dialect: "pg";}>
users = toDrizzlePGTable<User, string, {}>(tableName: string, tableConfig?: {}, mapperConfig?: DrizzleMapperConfig, type?: ReceiveType<T>): PgTableWithColumns<{ name: string; schema: undefined; columns: { id: FormatUUIDv7; email: FormatEmail; name: PgColumn<{ name: "name"; tableName: string; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; bio: PgColumn<...>; age: PgColumn<...>; createdAt: PgColumn<...>; }; dialect: "pg";}>
toDrizzlePGTable<User>('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: string; schema: undefined; columns: { id: FormatUUIDv7; email: FormatEmail; name: PgColumn<{ name: "name"; tableName: string; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; identity: undefined; generated: undefined; }, {}, {}>; bio: PgColumn<{ name: "bio"; tableName: string; dataType: "string"; columnType: "PgText"; data: string; driverParam: string; notNull: false; hasDefault: false; isPrimaryKey: false; ... 5 more ...; generated: undefined; }, {}, {}>; age: PgColumn<...>; createdAt: PgColumn<...>; }; dialect: "pg";}>
users.id: PgColumn<{ name: any; tableName: string; dataType: any; columnType: any; data: any; driverParam: any; notNull: false; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: any; baseColumn: never; identity: undefined; generated: undefined;}, {}, { [x: string]: any; [x: number]: any; [x: symbol]: any;}>
id;Binary Serialization 🚀
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.
Write Once Run Everywhere
Run mion APIs in Node.js, Bun or Serverless platforms like Aws Lambda, Google Cloud Functions, Cloudflare Workers and Vercel.
Seamless Integration

Solid Performance
- RPC-style routing - No URL parsing or regex matching, just direct in-memory Map lookup
- JIT-compiled validation/serialization - RunTypes generates optimized functions at startup
- Fast cold starts - Load routes in demand, no need to load all routes and jit functions at startup
- Lightweight architecture - Simple request/response handling
