From 96c984bc99c81e7669770e8aa4f42e53a05b2aaa Mon Sep 17 00:00:00 2001 From: Rak Siva Date: Tue, 4 Mar 2025 11:59:59 -0700 Subject: [PATCH 1/3] Add Zod guide --- dictionary.txt | 1 + docs/guides/nodejs/zod.mdx | 249 +++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 docs/guides/nodejs/zod.mdx diff --git a/dictionary.txt b/dictionary.txt index 6f1218f5a..8ab824680 100644 --- a/dictionary.txt +++ b/dictionary.txt @@ -221,6 +221,7 @@ ctx reproducibility misconfigurations misconfiguration +dequeuing DSL LM 1B diff --git a/docs/guides/nodejs/zod.mdx b/docs/guides/nodejs/zod.mdx new file mode 100644 index 000000000..7650ef94a --- /dev/null +++ b/docs/guides/nodejs/zod.mdx @@ -0,0 +1,249 @@ +--- +description: 'Server-side validation with Zod across APIs, Schedules and Queues' +tags: + - API + - Realtime & Websockets +languages: + - typescript + - javascript +published_at: 2025-03-05 +updated_at: 2025-03-05 +--- + +# A Unified Approach to validation across APIs, Queues & Jobs with Zod + +On frontend applications validation can help users enter data in the correct format this stops them from providing empty values, invalid emails, or other common input mistakes. + +Once data leaves the browser we still need to ensure that the data remains valid: + +- What happens when services communicate asynchronously? +- How do we prevent bad data from breaking downstream systems? + +Cloud applications don’t just receive requests from web forms. Data comes from APIs, message queues, scheduled tasks, or WebSockets—each introducing the risk of incomplete, malformed, or even malicious data. + +Server-side validation is critical—not just for security, but for maintaining data integrity across the system. + +With Zod, we can define strict validation rules once and enforce them across APIs, events, and background jobs. + +## Defining a validation schema with Zod + +A Zod schema acts as a contract for how data should look. The example below defines a `userSchema`, which establishes clear rules for incoming user data. + +```ts +import { z } from 'zod' + +const userSchema = z.object({ + name: z.string().min(2, 'Name must be at least 2 characters'), + email: z.string().email('Invalid email format'), + age: z.number().min(18, 'Must be at least 18 years old'), +}) +``` + +### Breaking it down: + +- `name` must be a string and at least 2 characters long. If it's too short, an error message is returned. +- `email` must follow a valid email format, and Zod will automatically enforce this validation. +- `age` must be a number and at least 18. If the value is below this threshold, an error message is returned. + +This schema ensures that any user data passed into the application follows these strict rules. + +## Validating data + +Once the schema is defined, we can use `.safeParse()` to validate incoming data before using it in our application. + +```ts +const validUser = userSchema.safeParse({ + name: 'Alice', + email: 'alice@example.com', + age: 25, +}) +``` + +### What happens here? + +- The `safeParse()` method checks if the input matches the `userSchema`. +- If validation **passes**, the `validUser` object will contain the parsed data. +- If validation **fails**, Zod returns an object containing detailed error messages instead of throwing an exception. + + + Alternatively, use .parse(), which throws an error that can be handled + appropriately. +{' '} + +## Applying validation to an API endpoint + +Now, let’s use our schema apply server-side validation to an API. + +The following API route: + +1. Extracts the request body from the incoming request +2. Validates the data against `userSchema` +3. Returns a 400 error if validation fails + +```ts +import { api } from '@nitric/sdk' +import { z } from 'zod' + +const userSchema = z.object({ + name: z.string().min(2, 'Name must be at least 2 characters'), + email: z.string().email('Invalid email format'), + age: z.number().min(18, 'Must be at least 18 years old'), +}) + +const usersApi = api('users') + +usersApi.post('/', async (ctx) => { + const result = userSchema.safeParse(ctx.req.json()) // Validate request + if (result.success) { + ctx.res.json({ message: 'User validated.', result }) + } else { + ctx.res.status = 400 + ctx.res.json(result.error) + } +}) +``` + +### Why this matters: + +- If the request body is **valid**, the API processes the request as usual. +- If **invalid**, the response includes detailed validation errors, ensuring that only properly formatted data is accepted. + +### Example invalid request: + +```json +{ + "name": "test", + "email": "test@test.com", + "age": 12 +} +``` + +### Expected response: + +```json +{ + "issues": [ + { + "code": "too_small", + "minimum": 18, + "type": "number", + "inclusive": true, + "exact": false, + "message": "Must be at least 18 years old", + "path": ["age"] + } + ], + "name": "ZodError" +} +``` + +APIs aren’t the only place validation matters. Cloud applications move data through multiple services—including message queues and scheduled tasks. + +## Validating and processing messages in a queue + +If we don’t validate messages before processing them, bad data can propagate throughout the system. + +Let’s say we have an `orders` queue where payment services publish transaction messages. To maintain integrity, we must validate each order before processing it. + +### Defining the validation schema + +```ts +import { api, queue, schedule } from '@nitric/sdk' +import { z } from 'zod' + +const orderSchema = z.object({ + orderId: z.string().uuid(), + amount: z.number().positive(), + userId: z.string().min(1), +}) +``` + +### Key Rules: + +- `orderId` must be a valid UUID +- `amount` must be a positive number +- `userId` must be a string with at least one character + +This schema ensures that invalid orders never enter the system. + +## Applying validation in a queue handler + +Next, we define an API that accepts orders and enqueues them only if they pass validation. + +```ts +const orderApi = api('orders') +const ordersQueue = queue('orders').allow('dequeue', 'enqueue') + +const orderSchema = z.object({ + orderId: z.string().uuid(), + amount: z.number().positive(), + userId: z.string().min(1), +}) + +orderApi.post('/', async (ctx) => { + const result = orderSchema.safeParse(ctx.req.json()) + if (result.success) { + await ordersQueue.enqueue(result.data) + ctx.res.json({ + message: `Adding order with id: ${result.data.orderId} to queue`, + }) + } else { + ctx.res.status = 400 + ctx.res.json(result.error) + } +}) +``` + +### Breakdown: + +- The request body is validated against `orderSchema` +- If the data is valid, it gets enqueued for processing +- If invalid, a `400` error response is returned + +This prevents bad data from ever reaching the queue. + +## Processing the Queue + +To ensure only valid data is processed, we apply validation again when dequeuing orders. + +```ts +schedule('process-transactions').every('5 minutes', async (ctx) => { + console.log(`Processing at ${new Date().toLocaleString()}`) + + const tasks = await ordersQueue.dequeue() + + await Promise.all( + tasks.map(async (task) => { + const result = orderSchema.safeParse(task.payload) + + if (result.success) { + console.log( + `Processing order ${result.data.orderId} for user ${result.data.userId}`, + ) + await task.complete() + } else { + console.error(`Invalid order message:`, result.error) + } + }), + ) +}) +``` + +### What This Does: + +- Every **5 minutes**, the function processes all messages in the queue +- Each message is validated before being processed +- Invalid messages are logged and ignored to prevent errors in downstream services + +## Conclusion + +Validation isn't just about catching errors, it’s about building confidence in how data moves through an application. + +By integrating Zod into a Nitric application, we: + +- Ensure APIs only accept well-formed requests +- Prevent malformed messages from breaking queue processing +- Guarantee background jobs run with predictable data +- Validate real-time connections before they interact with the system + +Now that you've established server-side validation, the next step is handling errors effectively, some errors and failures will require retries, while others should be logged and flagged for manual review. From d1a6caaab6ee4b94cb97501d9ecc45ba76fddb15 Mon Sep 17 00:00:00 2001 From: Rak Date: Wed, 5 Mar 2025 09:25:37 -0700 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Ryan Cartwright <39504851+HomelessDinosaur@users.noreply.github.com> --- docs/guides/nodejs/zod.mdx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/guides/nodejs/zod.mdx b/docs/guides/nodejs/zod.mdx index 7650ef94a..d5ffe4950 100644 --- a/docs/guides/nodejs/zod.mdx +++ b/docs/guides/nodejs/zod.mdx @@ -12,7 +12,7 @@ updated_at: 2025-03-05 # A Unified Approach to validation across APIs, Queues & Jobs with Zod -On frontend applications validation can help users enter data in the correct format this stops them from providing empty values, invalid emails, or other common input mistakes. +Validation can help users enter data in the correct format when interacting with front-end applications. This prevents them from providing empty values, invalid emails, or other common input mistakes. Once data leaves the browser we still need to ensure that the data remains valid: @@ -23,7 +23,7 @@ Cloud applications don’t just receive requests from web forms. Data comes from Server-side validation is critical—not just for security, but for maintaining data integrity across the system. -With Zod, we can define strict validation rules once and enforce them across APIs, events, and background jobs. +With [Zod](https://zod.dev/), we can define strict validation rules once and enforce them across APIs, events, and background jobs. ## Defining a validation schema with Zod @@ -39,11 +39,6 @@ const userSchema = z.object({ }) ``` -### Breaking it down: - -- `name` must be a string and at least 2 characters long. If it's too short, an error message is returned. -- `email` must follow a valid email format, and Zod will automatically enforce this validation. -- `age` must be a number and at least 18. If the value is below this threshold, an error message is returned. This schema ensures that any user data passed into the application follows these strict rules. From 7b4abcd575f5d8866c75917ef240b2ae9899e882 Mon Sep 17 00:00:00 2001 From: David Moore <4121492+davemooreuws@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:29:31 +1100 Subject: [PATCH 3/3] Apply suggestions from code review --- docs/guides/nodejs/zod.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/nodejs/zod.mdx b/docs/guides/nodejs/zod.mdx index d5ffe4950..6e2328997 100644 --- a/docs/guides/nodejs/zod.mdx +++ b/docs/guides/nodejs/zod.mdx @@ -6,8 +6,8 @@ tags: languages: - typescript - javascript -published_at: 2025-03-05 -updated_at: 2025-03-05 +published_at: 2025-03-24 +updated_at: 2025-03-24 --- # A Unified Approach to validation across APIs, Queues & Jobs with Zod