19

I am using Zod inside my Express & TypeScript & Mongoose API project and when trying to validate my user input against the user schema it returns types conflicts:

  Argument of type '{ firstName?: string; lastName?: string; password?: string; passwordConfirmation?: string; email?: string; }' is not assignable to parameter of type 'UserInput'.
      Property 'email' is optional in type '{ firstName?: string; lastName?: string; password?: string; passwordConfirmation?: string; email?: string; }' but required in type 'UserInput'

Here is the schema def:

export const createUserSchema = object({
  body: object({
    firstName: string({
      required_error: 'First name is required',
    }),
    lastName: string({
      required_error: 'Last name is required',
    }).nonempty(),
    password: string({
      required_error: 'Password is required',
    })
      .nonempty()
      .min(6, 'Password too short - should be 6 chars minimum'),

    passwordConfirmation: string({
      required_error: 'Confirm password is required',
    }),
    email: string({
      required_error: 'Email is required',
    })
      .email('Not a valid email')
      .nonempty(),
  }).refine((data) => data.password === data.passwordConfirmation, {
    message: 'Passwords do not match',
    path: ['passwordConfirmation'],
  }),
});

export type CreateUserInput = Omit<TypeOf<typeof createUserSchema>, 'body.passwordConfirmation'>;


export interface UserInput {
  email: string;
  firstName: string;
  lastName: string;
  password: string;
}

How to make these Zod schema fields all not optional as it is making it optional by default?

Ali H. Kudeir
  • 756
  • 2
  • 9
  • 19

3 Answers3

35

This might be caused by not using strict: true in TypeScript compiler options as referred to installation section in README file.

Following basic tsconfig.json file would fix that:

{
    "compilerOptions": {
        "strict": true   
    }
}
pr0gramist
  • 8,305
  • 2
  • 36
  • 48
  • 2
    Can we achieve this without strict mode? I mean... I'm working in quite a huge project that would require a lot of work to change that... – caeus Sep 15 '22 at 17:32
  • 3
    @caeus seems like "strictNullChecks": true instead of "strict": true is enough for non-optional fields. While this probably still force you to migrate some of code I think it will be much easier – pr0gramist Sep 18 '22 at 16:18
  • I had set `"strict": true` but then saw later down the config I had set `"strictNullChecks": false` which threw me for a loop. Setting both to `true` fixed this issue for me. Hope that helps someone else! – Artokun Sep 29 '22 at 07:42
3

As pointed out by @pr0gramist in the comments, the accepted answer should be to add this setting in your tsconfig.json:

{
    "compilerOptions": {
        "strictNullChecks": true   
    }
}

Depending on your default zod configuration, you may need to add this in your schemas as well:

requiredField: z.string().nonempty()

This method is less intrusive than a "strict": true, which adds more typescript constraints to your code base.

David
  • 1,842
  • 2
  • 21
  • 31
2

I suggest to create a basic version and then extend it with 'passwordConfirmation' and relative refinement. Also is important to 'infer' from zod to typescript, to make available typescript linting.

import { object, string, z } from 'zod';

export const userSchema = object({
  firstName: string({
    required_error: 'First name is required',
  }),
  lastName: string({
    required_error: 'Last name is required',
  }).nonempty(),
  password: string({
    required_error: 'Password is required',
  })
    .nonempty()
    .min(6, 'Password too short - should be 6 chars minimum'),
  email: string({
    required_error: 'Email is required',
  })
    .email('Not a valid email')
    .nonempty(),
});
type userSchema = z.infer<typeof userSchema>;

export const createUserSchema = object({
  body: userSchema
    .extend({
      passwordConfirmation: string({
        required_error: 'Confirm password is required',
      }),
    })
    .refine((data) => data.password === data.passwordConfirmation, {
      message: 'Passwords do not match',
      path: ['passwordConfirmation'],
    }),
});
type createUserSchema = z.infer<typeof createUserSchema>;

const us: userSchema = {
  email: '',
  firstName: '',
  lastName: '',
  password: '',
};

const cui: createUserSchema = {
  body: {
    email: '',
    firstName: '',
    lastName: '',
    password: '',
    passwordConfirmation: '',
  },
};
dev_hero
  • 194
  • 1
  • 7