Relativo a sua pergunta, eu também uso o zod para validar o formato de dados no back- end. Eu custumo usar o seguinte formato:

// Autenticação do controller
  const authenticateBodySchema = z.object({
    email: z.string().email(),
    password: z.string().min(6),
  });

  const { email, password } = authenticateBodySchema.parse(req.body);

  try {
    ... // Return status 200 to success
  } catch (err) {
    if (err instanceof InvalidCredentialsError) {
    // return status 400 with invalid credentials (email or password)
    }

    throw err;
  }

Nesse caso eu apenas valido com o zod se o formato dos dados está certo. Se o parse der erro eu intercepto esse erro e trato isso como em um handler em uma camada acima:

  if (error instanceof ZodError) {
  // return status 400 with Validation Error and zod message (error.format())

Outra coisa é que se o usuário errou o email ou a senha, eu não comento onde ele errou. Eu retorno apenas um 400 com InvalidCredentials. Dessa forma, caso alguém estiver tentando invadir a conta de um usuário ele não vai saber onde está o erro, se foi no email ou na senha.

Além disso, eu começaria a usar o status code 422 depois que o usuário estivesse logado para outros recursos do sistema que o usuário tentasse acessar.

vc está qual framework? pq no nest tem um interceptor que válida entrada de dados automaticamente.

@Module({
  imports: [AuthModule, UserModule],
  controllers: [],
  providers: [
    { provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor },
    { provide: APP_PIPE, useClass: ZodValidationPipe },
  ],
})
export class AppModule {}
Olá `MikeColuti`. Eu estava falando do `Nest` mesmo. Eu estou trabalhando em um projeto com back feito em `Nest`. Não sabia que tinha esse interceptador com o `zod`. Acho que vou colocar uma melhoria no projeto que trabalho depois.
Muito bom, tb não sabia dos interceptors... vlw pelo apontamento.