@kaapi/validator-arktype
v0.0.42
Published
ArkType-powered request validation and documentation plugin for Kaapi.
Maintainers
Readme
🧪 @kaapi/validator-arktype
ArkType-powered validation plugin for Kaapi. Validate request params, payload, query, headers, and state using ArkType schemas. Includes built-in documentation helpers for seamless API docs generation.
⚠️ This library is ESM‑only. It requires an environment that supports ESM imports.
🚀 Installation
npm install @kaapi/validator-arktype📦 Peer Dependency
Requires ArkType:
npm install arktype@^2.1.25🛠️ Usage
🔌 Register the Plugin
import { Kaapi } from '@kaapi/kaapi';
import { validatorArk } from '@kaapi/validator-arktype';
import { type } from 'arktype';
const app = new Kaapi({
port: 3000,
host: 'localhost',
docs: {
disabled: false, // explicitly enables documentation generation
},
});
await app.extend(validatorArk); // register the plugin📐 Define a Schema
import { ValidatorArkSchema } from '@kaapi/validator-arktype';
import { type } from 'arktype';
const routeSchema: ValidatorArkSchema = {
payload: type({
name: 'string',
}),
};🧭 Create a Route
app.base()
.ark(routeSchema)
.route(
{
method: 'POST',
path: '/items',
},
(req) => ({ id: Date.now(), name: req.payload.name })
);
// or using inline handler
/*
app.base().ark(routeSchema).route({
method: 'POST',
path: '/items',
handler: req => ({ id: Date.now(), name: req.payload.name })
})
*/🧱 Build Routes Separately with withSchema
You can use withSchema to create validated routes without directly chaining from app.base().
This cleanly separates route construction from app registration.
import { withSchema } from '@kaapi/validator-arktype'
import { type } from 'arktype';
const schema = {
payload: type({
name: 'string'
})
}
const route = withSchema(schema).route({
method: 'POST',
path: '/items',
handler: req => ({ id: Date.now(), name: req.payload.name })
})
// later, during app setup
app.route(route)This is the most flexible and convenient way to use @kaapi/validator-arktype when building modular APIs.
⚙️ Advanced Configuration
🚨 failAction
Control how validation failures are handled:
| Value | Behavior | Safe? | Description |
| ---------- | ---------------------------- | ------------------------- | ------------------------------- |
| 'error' | Reject with validation error | ✅ | Default safe behavior |
| 'log' | Log and reject | ✅ | Useful for observability |
| function | Custom handler | ✅ (developer-controlled) | Must return or throw explicitly |
| 'ignore' | ❌ Not supported | ❌ | Unsafe and not implemented |
🧪 Example with Overrides
You can override ArkType validation behavior globally for all routes, or per route as needed.
🔁 Global Override (All Routes)
const app = new Kaapi({
// ...
routes: {
plugins: {
ark: {
failAction: 'log',
},
},
},
});
await app.extend(validatorArk);This logs validation errors before throwing them for all ArkType-validated routes.
🔂 Per-Route Override
app.base()
.ark({
query: type({
name: type(['string', '@', 'Optional name to personalize the greeting response'])
.pipe((v) => v?.trim() ?? '')
.to('0 < string <= 10')
.default('World')
}),
failAction: async (request, h, err) => {
if (Boom.isBoom(err)) {
return h
.response({
...err.output.payload,
details: err.data?.validationError?.issues,
})
.code(err.output.statusCode)
.takeover();
}
return err;
},
})
.route({
path: '/greetings',
method: 'GET',
handler: ({ query: { name } }) => `Hello ${name}!`,
});📤 File Upload Example
Multipart file uploads with ArkType validation is supported. Here's how to validate an uploaded image file and stream it back in the response:
app.base()
.ark({
payload: type({
file: type({
_data: type.instanceOf(Buffer),
hapi: type({
filename: 'string',
headers: {
'content-type': '\'image/jpeg\' | \'image/jpg\' | \'image/png\'',
},
}),
}),
}),
})
.route(
{
method: 'POST',
path: '/upload-image',
options: {
description: 'Upload an image',
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data',
multipart: { output: 'stream' },
maxBytes: 1024 * 3_000,
},
},
},
(req, h) => h.response(req.payload.file._data).type(req.payload.file.hapi.headers['content-type'])
);🧾 Notes
type({ ... })is used to accommodate the structure of multipart file metadata.- The
_data: type.instanceOf(Buffer)field is automatically interpreted as a binary field by the documentation generator. - This ensures correct OpenAPI and Postman documentation is generated, with the file field shown as a binary upload.
- The route streams the uploaded image back with its original content type.
🔄 Flexible API Design
Prefer Joi or migrating gradually? No problem.
You can still use app.route(...) with Joi-based validation while adopting ArkType via app.base().ark(...).route(...). This dual-mode support ensures graceful evolution, allowing traditional and modern routes to coexist without breaking changes.
📚 License
MIT
This package is tested as part of the Kaapi monorepo. See the main Kaapi README for coverage details.
🤝 Contributing
Contributions, issues, and feature requests are welcome! Feel free to open a discussion or submit a pull request.
