Joi to TypeScript Interface Converter
joi-to-typescript is a utility library designed to automatically generate TypeScript interfaces from Joi validation schemas. Its primary purpose is to eliminate the redundancy of manually defining both Joi schemas for runtime validation and TypeScript interfaces for compile-time type checking, adhering to the DRY (Don't Repeat Yourself) principle. The current stable version is 4.15.0, with frequent minor and patch releases addressing dependency updates and feature enhancements. It is built to work seamlessly with Joi v17.x and is particularly useful in ecosystems like Hapi.js, offering integrations with tools like `joi-to-swagger` and `hapi-swagger` by leveraging Joi's `.meta()` functionality for interface naming and structure.
Common errors
-
TypeError: joi_1.default.validate is not a function
cause Incorrect Joi import statement (e.g., using `require` for a module that only exports ES modules) or a mismatch in Joi versions between runtime and type definitions.fixEnsure you are using `import Joi from 'joi';` for ESM projects and that `joi@17.x` is correctly installed. -
Error: Cannot find module 'joi'
cause The `joi` package, a required peer dependency, is not installed in the project's dependencies.fixInstall Joi: `npm install joi` or `yarn add joi`. -
Generated TypeScript interface is empty or 'any'
cause The Joi schema might be missing `.meta({ className: 'InterfaceName' })`, or it's a complex schema feature not fully supported by `joi-to-typescript` yet, or Joi's `.label()` was used instead of `.meta()` for naming.fixEnsure all root-level schemas intended to become interfaces have `.meta({ className: 'YourInterfaceName' })`. Check the documentation for supported Joi features and consider breaking down very complex schemas if issues persist.
Warnings
- gotcha This package requires Joi version 17.x as a peer dependency. Using older or incompatible versions of Joi will likely result in conversion errors or incorrect type generation.
- gotcha joi-to-typescript is intended as a development-time tool. It should be installed as a `devDependency`, while `joi` itself (the peer dependency) should be installed as a regular `dependency`.
- gotcha For defining interface names, always use `.meta({ className: 'YourInterfaceName' })` on your Joi schemas. Using `.label()` for this purpose is discouraged as it interferes with Joi's intended use for error messages and might lead to unexpected behavior or conflicts with other tools like `hapi-swagger`.
- breaking The library explicitly supports Node.js versions 18 and 20. While the `engines` field in `package.json` might indicate `>=14.0.0`, official support focuses on these newer LTS versions. Using older Node.js environments may lead to unexpected issues.
- gotcha When dealing with Joi objects that allow unknown keys via `.unknown()`, you can specify the type for these unknown keys using `.meta({ unknownType: 'your_type' })` to ensure the generated TypeScript interface accurately reflects this. Without it, the generated type for unknown keys might default to `any` or be less specific.
Install
-
npm install joi-to-typescript -
yarn add joi-to-typescript -
pnpm add joi-to-typescript
Imports
- convert
const { convert } = require('joi-to-typescript');import { convert } from 'joi-to-typescript'; - Joi
const Joi = require('joi');import Joi from 'joi';
- ITypeInfo
import { ITypeInfo } from 'joi-to-typescript';
Quickstart
import Joi from 'joi';
import { convert } from 'joi-to-typescript';
// Define your Joi schemas with .meta({ className: '...' }) for interface names
export const JobSchema = Joi.object({
businessName: Joi.string().required(),
jobTitle: Joi.string().required()
}).meta({ className: 'Job' });
export const WalletSchema = Joi.object({
usd: Joi.number().required(),
eur: Joi.number().required()
})
.unknown() // Example with unknown keys
.meta({ className: 'Wallet', unknownType: 'number' }); // Specify type for unknown keys
export const PersonSchema = Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required().description('Last Name'),
job: JobSchema, // Nested schema
wallet: WalletSchema
}).meta({ className: 'Person' });
// Use the convert function to generate TypeScript interfaces
const { typescript: jobInterface } = convert(JobSchema);
const { typescript: walletInterface } = convert(WalletSchema);
const { typescript: personInterface } = convert(PersonSchema);
console.log('// Job Interface');
console.log(jobInterface);
console.log('\n// Wallet Interface');
console.log(walletInterface);
console.log('\n// Person Interface');
console.log(personInterface);
/* Expected output will resemble:
// Job Interface
export interface Job {
businessName: string;
jobTitle: string;
}
// Wallet Interface
export interface Wallet {
usd: number;
eur: number;
[x: string]: number;
}
// Person Interface
export interface Person {
firstName: string;
lastName: string;
job?: Job;
wallet?: Wallet;
}
*/