
Metamorphic, type-safe validation engine for TypeScript & JavaScript
Unified runtime, builder, decorator, and string rule support. Generate OpenAPI/JSON Schema. Use in Node.js, React, Express, NestJS, and more.
Gigli.js is not just another validator. It's a metamorphic engine that adapts to your coding style—builder, decorator, or string rules—without sacrificing type safety, power, or extensibility.
- 🧩 Unified API: Mix & match builder, decorator, and string rules
- 🦾 Type Inference: Full TypeScript support, everywhere
- 🛠️ Extensible: Custom rules, transformers, and definitions
- 🔍 Detailed Error Tracing: See exactly why validation failed
- 🏗️ Schema Generation: OpenAPI & JSON Schema out of the box
- ⚡ Zero dependencies, works in Node.js, browsers, and modern runtimes
npm install gigli.js
import { v } from 'gigli.js';
const UserSchema = v.object({
username: v.string().min(3),
email: v.string().email(),
});
const result = UserSchema.safeParse({ username: 'bob', email: 'bob@email.com' });
console.log(result.success); // true
import { v } from 'gigli.js';
const UserSchema = v.object({
username: v.string().min(3),
email: v.string().email(),
});
UserSchema.parse({ username: 'ab', email: 'bad' }); // Throws with detailed error
type User = v.infer<typeof UserSchema>;
// User: { username: string; email: string }
try {
UserSchema.parse({ username: 'ab', email: 'bad' });
} catch (err) {
console.log(err.flatten());
/*
{
input: { username: 'ab', email: 'bad' },
errors: [
{ path: ['username'], message: 'String must be at least 3 characters' },
{ path: ['email'], message: 'Invalid email address' }
]
}
*/
}
const PostSchema = v.object({
id: v.string().uuid(),
title: v.string().min(5).max(100),
tags: v.array(v.string().min(2)).optional(),
author: UserSchema, // Schemas are composable!
status: v.string().from('enum:values=draft|published|archived'),
meta: v.union([
v.object({ type: v.literal('text'), content: v.string() }),
v.object({ type: v.literal('image'), url: v.string().url() })
])
});
const BlogSchema = v.object({
posts: v.array(PostSchema),
owner: v.object({
id: v.string().uuid(),
name: v.string(),
roles: v.array(v.string().from('enum:values=admin|editor|user')),
}),
settings: v.object({
commentsEnabled: v.boolean().optional(),
theme: v.string().default('light'),
})
});
import { v, ValidatedModel } from 'gigli.js';
@v.Refine((dto) => dto.password === dto.passwordConfirm, {
message: "Passwords don't match",
path: ['passwordConfirm'],
})
class CreateUserDto extends ValidatedModel {
@v.Rule(v.string().email())
email: string;
@v.Rule('string:min=8,max=50')
password: string;
@v.Rule(v.string())
passwordConfirm: string;
}
const userDto = CreateUserDto.from({
email: 'foo@bar.com',
password: 'secret123',
passwordConfirm: 'secret123',
});
const OrderPipeline = v.pipeline()
.transform((data) => ({ ...data, orderId: data.id.toLowerCase() }))
.validate(v.object({ orderId: v.string().min(1) }))
.dispatch('paymentMethod', {
'credit_card': v.object({ card: v.string().creditCard() }),
'paypal': v.object({ email: v.string().email() }),
})
.refine((order) => order.total > 0, { message: 'Order total must be positive' })
.effect({
onSuccess: (data) => console.log('Order Validated', data.orderId),
onFailure: (trace) => console.error('Order Failed', trace),
});
const result = OrderPipeline.safeParse(orderData);
v.registerRule('isEven', (value) => typeof value === 'number' && value % 2 === 0);
v.registerTransformer('trim', (value) => typeof value === 'string' ? value.trim() : value);
v.define('slug', 'string:min=3|regex:^[a-z0-9-]+$');
const SlugSchema = v.string().from('slug').transform('trim');
npx gigli codegen --schema ./src/schemas.ts --target openapi
npx gigli codegen --schema ./src/schemas.ts --target jsonschema
npx gigli analyze --schema ./src/schemas.ts
npx gigli --help
Feature | Zod | Yup | class-validator | Gigli.js |
---|---|---|---|---|
Type Inference | ✅ | ❌ | ✅ | ✅ |
Chainable Schema Builder | ✅ | ✅ | ❌ | ✅ |
Decorator API | ❌ | ❌ | ✅ | ✅ |
Portable String Rules | ❌ | ❌ | ❌ | ✅ |
Unified Runtime (Mix & Match) | ❌ | ❌ | ❌ | ✅ |
Validation Pipelines & Dispatch | ❌ | ❌ | ❌ | ✅ |
Detailed Error Tracing | ❌ | ❌ | ❌ | ✅ |
Auto OpenAPI/JSON Schema Gen | ❌ | ❌ | ❌ | ✅ |
Extensible (Rules/Transformers) | ✅ |
- Node.js, Deno, Bun, Cloudflare Workers
- React, Vue, Svelte, Solid
- Express, NestJS, tRPC, REST, GraphQL
- Works in browsers and modern runtimes
- 📖 Full Usage Guide
- 🧩 API Reference: See above and in-code docs
- 💡 Examples: examples/
- 📝 Contributing Guide
- 🐞 Report Issues
- 📦 NPM Package
- ⚖️ License (MIT)
We are building the future of data validation, and we'd love your help! Please read our CONTRIBUTING.md to get started. Whether it's a bug report, a new feature, or a documentation improvement, all contributions are welcome!
Gigli.js is open-source software licensed under the MIT License.
validation, validator, typescript, schema, zod, yup, class-validator, openapi, jsonschema, decorators, cli, nodejs, react, express, nestjs, type-safe, builder, portable, runtime, inference, extensible, pipeline, unified, metamorphic
import { v } from 'gigli.js';
const UserSchema = v.object({
username: v.string().min(3),
email: v.string().email(),
});
(async () => {
const result = await UserSchema.safeParse({ username: 'bob', email: 'bob@email.com' });
console.log('safeParse:', result); // { success: true, data: ..., error: null }
try {
const parsed = await UserSchema.parse({ username: 'bob', email: 'bob@email.com' });
console.log('parse:', parsed); // { username: 'bob', email: 'bob@email.com' }
} catch (err) {
console.error('parse error:', err);
}
})();
const { v } = require('gigli.js');
const UserSchema = v.object({
username: v.string().min(3),
email: v.string().email(),
});
(async () => {
const result = await UserSchema.safeParse({ username: 'bob', email: 'bob@email.com' });
console.log('safeParse:', result); // { success: true, data: ..., error: null }
try {
const parsed = await UserSchema.parse({ username: 'bob', email: 'bob@email.com' });
console.log('parse:', parsed); // { username: 'bob', email: 'bob@email.com' }
} catch (err) {
console.error('parse error:', err);
}
})();
TypeScript types are included automatically. You can use the same import as ESM:
import { v } from 'gigli.js';
// ...rest of your code
schema.safeParse(data)
— Returns{ success, data, error }
. Does not throw.schema.parse(data)
— Returns parsed data or throws on error.
Both methods are async and must be awaited.
- GitHub: https://github.com/jasgigli/gigli.js
- Twitter: @jasgiigli
- LinkedIn: jasgigli