Skip to content

Latest commit

 

History

History
134 lines (105 loc) · 3.2 KB

File metadata and controls

134 lines (105 loc) · 3.2 KB

Koa

ata drops into Koa as a small middleware. Koa's ctx pattern keeps the wiring short.

Install

npm install ata-validator koa @koa/router koa-bodyparser

Body validation middleware

const 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)

Abort-early for public endpoints

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.

Buffer path (raw body)

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.

Query / params validation

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)

Error mapping

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
}

Notes

  • Hold the Validator at module scope. Do not instantiate inside the middleware factory return.
  • For JSON files read at startup, pair with ata compile to skip the runtime ata-validator dependency on serverless platforms.
  • The coerceTypes: true and removeAdditional: true options still apply and mutate ctx.request.body in place.