Skip to content

Latest commit

 

History

History
128 lines (99 loc) · 3.2 KB

File metadata and controls

128 lines (99 loc) · 3.2 KB

Express

ata fits into Express as a small middleware factory. Validate the parsed body before the route handler sees it.

Install

npm install ata-validator express

Body validation middleware

const express = require('express')
const { Validator } = require('ata-validator')

function ataBody(schema, opts = {}) {
  const v = new Validator(schema, opts)
  return (req, res, next) => {
    const r = v.validate(req.body)
    if (!r.valid) {
      return res.status(400).json({ error: 'validation failed', errors: r.errors })
    }
    next()
  }
}

const app = express()
app.use(express.json())

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'integer', minimum: 1 },
    name: { type: 'string', minLength: 1 },
    email: { type: 'string' },
  },
  required: ['id', 'name', 'email'],
}

app.post('/users', ataBody(userSchema), (req, res) => {
  res.json({ ok: true, id: req.body.id })
})

app.listen(3000)

Abort-early variant

For high-throughput route guards that only need reject / accept:

app.post('/ingest', ataBody(userSchema, { abortEarly: true }), handler)

On failure the client receives { errors: [{ message: 'validation failed' }] } without the detailed paths, but the invalid path runs ~4x faster per request.

Buffer path (raw body)

If the route has bodyParser.raw({ type: 'application/json' }) the body arrives as a Buffer. Use simdjson validation and skip JSON.parse for rejected payloads:

const { Validator } = require('ata-validator')

const v = new Validator(userSchema)

app.post(
  '/ingest',
  express.raw({ type: 'application/json', limit: '10mb' }),
  (req, res) => {
    if (!v.isValid(req.body)) {
      return res.status(400).json({ error: 'invalid' })
    }
    const parsed = JSON.parse(req.body.toString())
    // ...
  },
)

Useful for webhook endpoints where most traffic is untrusted.

Query and params

Same pattern, pointing at a different slot on the request:

function ataQuery(schema) {
  const v = new Validator(schema)
  return (req, res, next) => {
    const r = v.validate(req.query)
    if (!r.valid) return res.status(400).json({ errors: r.errors })
    next()
  }
}

app.get('/search', ataQuery({
  type: 'object',
  properties: { q: { type: 'string', minLength: 1 } },
  required: ['q'],
}), handler)

Error formatter

Default errors have ajv's shape (keyword, instancePath, params, message). For end-user responses, map to a flatter structure:

function ataBody(schema, opts = {}) {
  const v = new Validator(schema, opts)
  return (req, res, next) => {
    const r = v.validate(req.body)
    if (!r.valid) {
      const issues = r.errors.map((e) => ({
        field: e.instancePath.replace(/^\//, ''),
        message: e.message,
      }))
      return res.status(400).json({ error: 'validation failed', issues })
    }
    next()
  }
}

Notes

  • Works identically with Connect-style frameworks (Restify, server applications using Express-style middleware).
  • Hold the Validator instance at module scope to reuse the compiled validator across requests.
  • For schemas that change per request (very rare), instantiate inside the middleware; the lazy-compile path keeps construction under a microsecond.