Validate queue payloads

Validate payloads before enqueueing and before running queue handlers.

Validate payloads at two boundaries:

  • before you enqueue user input
  • inside the queue handler before you trust the payload

Queue exports two helpers for that split: readValidatedPayload() and readValidatedJob().

Validate input before enqueueing

Use readValidatedPayload() in the route or function that accepts input.

server/api/queues/welcome.post.ts
import { createError, readBody } from 'h3'
import { readValidatedPayload, runQueue } from '@vitehub/queue'

export default defineEventHandler(async (event) => {
  const { email } = await readValidatedPayload(await readBody(event), (input) => {
    if (!input || typeof input !== 'object' || typeof input.email !== 'string' || !input.email.includes('@')) {
      throw createError({
        statusCode: 400,
        statusMessage: 'Enter a valid email address.',
      })
    }

    return {
      email: input.email.trim().toLowerCase(),
    }
  })

  return runQueue('welcome-email', { email })
})

Validate again inside the queue handler

Use readValidatedJob() in the queue definition when the handler should only work with validated data.

server/queues/welcome-email.ts
import { defineQueue, readValidatedJob } from '@vitehub/queue'

export default defineQueue(async (rawJob) => {
  const job = await readValidatedJob(rawJob, (payload) => ({
    email: typeof payload?.email === 'string' ? payload.email.trim().toLowerCase() : '',
  }))

  return {
    id: job.id,
    payload: job.payload,
  }
})

Why validate twice

Validating before enqueueing protects the caller-facing edge.

Validating inside the handler protects the worker edge. Jobs might still come from tests, provider callbacks, replay tools, or older producers that bypass the original route.