mion uses @mionjs/run-types for automatic validation.
This is a powerful type system that extracts TypeScript type metadata at compile time and uses it at runtime.
No need to manage schemas or write extra code for validation.
All routes and middleFns parameters are automatically validated before the method gets called.
If validation fails, an RpcError is thrown with details about the validation errors and method gets never called.
import {Routes, route} from '@mionjs/router';
import {memoryStoreService} from './full-example.app.ts';
// Your TypeScript types ARE the validation schema
interface User {
id: string;
email: string;
age: number;
birthDate: Date;
tags: Set<string>;
}
type NewUser = Omit<User, 'id'>;
// mion automatically:
// 1. Restores Date and Set from JSON
// 2. Validates user parameter
const routes = {
createUser: route((ctx, user: NewUser): User => {
// user is already validated and types are restored
console.log(user.birthDate instanceof Date); // true
console.log(user.tags instanceof Set); // true
return memoryStoreService.createUser(user);
}),
} satisfies Routes;
If an invalid request is sent, a ValidationError (which extends RpcError) is thrown:
import {RpcError, RunTypeError, ValidationError} from '@mionjs/core';
// Example validation error thrown when invalid data is received
const validationError: ValidationError = new RpcError({
statusCode: 400,
type: 'validation-error',
publicMessage: "Invalid params in 'createUser', validation failed.",
errorData: {
typeErrors: [
{path: ['email'], expected: 'string'},
{path: ['age'], expected: 'number'},
] as RunTypeError[],
},
});
mion provides two main validation functions with different use cases:
isType is optimized for performance and returns a simple true or false result. Use this when you only need to know if data is valid without detailed error information.
import {createIsTypeFn} from '@mionjs/run-types';
interface User {
name: string;
age: number;
}
const isUser = await createIsTypeFn<User>();
isUser({name: 'John', age: 30}); // true
isUser({name: 'John'}); // false (missing age)
isUser({name: 'John', age: '30'}); // false (age is string)
When to use isType:
getTypeErrors returns comprehensive error data including the path to invalid properties and expected types. Use this when you need to provide detailed feedback to users or for debugging.
import {createTypeErrorsFn} from '@mionjs/run-types';
interface User {
name: string;
age: number;
}
const getUserErrors = await createTypeErrorsFn<User>();
const errors = getUserErrors({name: 123, age: 'invalid'});
// Returns: [
// { path: ['name'], expected: 'string', actual: 'number' },
// { path: ['age'], expected: 'number', actual: 'string' }
// ]
When to use getTypeErrors:
By default, mion allows objects with extra properties to pass validation. When strictTypes is enabled, objects with unknown or extra properties are rejected.
This is useful for security-sensitive endpoints where you want to ensure no unexpected data is passed.
import {createIsTypeFn, createTypeErrorsFn} from '@mionjs/run-types';
interface User {
name: string;
age: number;
}
// With strictTypes, extra properties are rejected
const isUser = await createIsTypeFn<User>({strictTypes: true});
isUser({name: 'John', age: 30}); // true
isUser({name: 'John', age: 30, extra: 'value'}); // false (unknown property 'extra')
// typeErrors also reports unknown properties
const getUserErrors = await createTypeErrorsFn<User>({strictTypes: true});
getUserErrors({name: 'John', age: 30}); // []
getUserErrors({name: 'John', age: 30, extra: 'value'});
// Returns: [{ path: ['extra'], expected: 'never' }]
You can enable strictTypes globally via router options or per-route:
import {Routes, route, initRouter} from '@mionjs/router';
interface User {
name: string;
email: string;
age: number;
}
// Enable strictTypes globally: rejects objects with unknown/extra properties
await initRouter({strictTypes: true});
// Or enable strictTypes per-route
const routes = {
// this route rejects objects with extra properties
createUser: route((ctx, user: User): User => user, {strictTypes: true}),
// this route accepts objects with extra properties
updateUser: route((ctx, user: Partial<User>): Partial<User> => user, {strictTypes: false}),
} satisfies Routes;
[key: string]: any), strictTypes is automatically skipped since the type explicitly allows arbitrary properties.