ata drops into Koa as a small middleware. Koa's ctx pattern keeps the wiring short.
npm install ata-validator koa @koa/router koa-bodyparserconst Koa = require('koa')
const Router = require('@koa/router')
const bodyParser = require('koa-bodyparser')
const { Validator } = require('ata-validator')
function ataBody(schema, opts = {}) {
const v = new Validator(schema, opts)
return async (ctx, next) => {
const r = v.validate(ctx.request.body)
if (!r.valid) {
ctx.status = 400
ctx.body = { error: 'validation failed', errors: r.errors }
return
}
await next()
}
}
const app = new Koa()
const router = new Router()
app.use(bodyParser())
const userSchema = {
type: 'object',
properties: {
id: { type: 'integer', minimum: 1 },
name: { type: 'string', minLength: 1 },
email: { type: 'string' },
},
required: ['id', 'name', 'email'],
}
router.post('/users', ataBody(userSchema), (ctx) => {
ctx.body = { ok: true, id: ctx.request.body.id }
})
app.use(router.routes())
app.listen(3000)router.post('/ingest', ataBody(userSchema, { abortEarly: true }), handler)Invalid requests return a stub error without walking the schema tree to produce detail, about 4x faster on the invalid path.
Koa does not buffer raw bodies by default. For webhook-style endpoints where you want to skip JSON.parse on invalid input, use raw-body:
const getRawBody = require('raw-body')
const { Validator } = require('ata-validator')
const v = new Validator(schema)
router.post('/ingest', async (ctx) => {
const buf = await getRawBody(ctx.req, { limit: '10mb' })
if (!v.isValid(buf)) {
ctx.status = 400
ctx.body = { error: 'invalid' }
return
}
const data = JSON.parse(buf.toString())
ctx.body = { ok: true, data }
})isValid(buffer) uses simdjson on the native addon path, which means malformed or mismatched payloads are rejected without a full JS object tree ever being built.
Same middleware, different input:
function ataQuery(schema) {
const v = new Validator(schema)
return async (ctx, next) => {
const r = v.validate(ctx.query)
if (!r.valid) {
ctx.status = 400
ctx.body = { errors: r.errors }
return
}
await next()
}
}
router.get('/search', ataQuery({
type: 'object',
properties: { q: { type: 'string', minLength: 1 } },
required: ['q'],
}), handler)ata errors follow the ajv shape. Map to something friendlier if your API surface expects flatter errors:
if (!r.valid) {
ctx.status = 400
ctx.body = {
error: 'validation failed',
issues: r.errors.map((e) => ({
field: e.instancePath.replace(/^\//, ''),
message: e.message,
})),
}
return
}- Hold the Validator at module scope. Do not instantiate inside the middleware factory return.
- For JSON files read at startup, pair with
ata compileto skip the runtime ata-validator dependency on serverless platforms. - The
coerceTypes: trueandremoveAdditional: trueoptions still apply and mutatectx.request.bodyin place.