Skip to content

Latest commit

 

History

History
89 lines (67 loc) · 2.43 KB

File metadata and controls

89 lines (67 loc) · 2.43 KB
title
API action validator

Overview

Validation in Ship is handled automatically by providing a schema to createEndpoint. When schema is present, the validate middleware auto-applies and merges body + files + query + params into ctx.validatedData.

For additional validation logic (e.g. checking uniqueness), add custom middleware functions to the middlewares array.

This repo uses **Zod 4**. Use `z.email()`, `z.url()`, `z.uuid()` instead of Zod 3's `z.string().email()`. Search existing schemas for patterns.

Examples

Basic validation with createEndpoint

import { z } from 'zod';

import createEndpoint from 'routes/createEndpoint';
import isPublic from 'middlewares/isPublic';

const schema = z.object({
  firstName: z.string().min(1, 'Please enter first name.').max(100),
  lastName: z.string().min(1, 'Please enter last name.').max(100),
  email: z.email('Email format is incorrect.'),
  password: z.string().min(6, 'Password must be at least 6 characters'),
});

export default createEndpoint({
  method: 'post',
  path: '/sign-up',
  schema,
  middlewares: [isPublic],

  async handler(ctx) {
    const { firstName, lastName, email, password } = ctx.validatedData;
    // ...action code
  },
});

Custom validation middleware

To add extra validation logic beyond the Zod schema, add a custom middleware to the middlewares array:

import { z } from 'zod';

import createEndpoint from 'routes/createEndpoint';
import isPublic from 'middlewares/isPublic';
import { userService } from 'resources/user';

const schema = z.object({
  email: z.email('Email format is incorrect.'),
  password: z.string().min(6, 'Password must be at least 6 characters'),
});

const checkUserExists = async (ctx, next) => {
  const { email } = ctx.validatedData;
  const exists = await userService.exists({ email });

  if (exists) {
    ctx.throwClientError({ email: 'User with this email is already registered' });
    return;
  }

  await next();
};

export default createEndpoint({
  method: 'post',
  path: '/sign-up',
  schema,
  middlewares: [isPublic, checkUserExists],

  async handler(ctx) {
    const { email, password } = ctx.validatedData;
    // ...action code
  },
});
**Avoid `ctx.assertError()`** in handlers — it's a TypeScript assertion function that causes TS2775 without explicit type annotations on `ctx`. Use `ctx.throwError()` + `return` instead. ```