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.

mion-router.ts
// 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 types
interface 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 params
const
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.

mion-client.ts
// 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 routes
const [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: Date
createdAt
;
Native Classes Like Set are automatically serialized/deserialized
const user: Useruser.
User.tags: Set<string>
tags
;
}// Type error: id must be a number
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('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
();
Argument of type 'string' is not assignable to parameter of type 'number'.

Routes Flows

Execute multiple routes in a single HTTP request. Batch API calls together, and Orchestrate router logic from the client.


GraphQL-like data composition with the simplicity of RPC — resolve relationships in a single request using serverMapFrom.
mion-routesFlow.ts
// 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.

mion-run-types.ts
// 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 types
const 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 structure
const
const user: User
user
= const mockUser: (opts?: Partial<RunTypeOptions>) => UsermockUser();
Validate data at runtime
const isUser: (value: any) => booleanisUser(const user: Useruser);
Serialize complex types (Date, Set, unions) to JSON
const
const json: string
json
= 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.

mion-drizzle.ts
// 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 functionality
interface 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 interface
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
.
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.

Seamless Integration

Most modern stacks are tools glued together — validation, routing, serialization, client generation — all wired manually. mion replaces that with a single integrated system, reducing boilerplate, inconsistencies, and maintenance overhead.

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

 

Copyright © 2026