npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

zod-to-nestjs-graphql

v1.0.0

Published

A package to generate GraphQL types from `zod` contracts.

Downloads

43

Readme

zod-to-nestjs-graphql

A package to generate GraphQL types from zod contracts.

Inspired by nestjs-graphql-zod package. Thanks to Tarık İnce!

How to use

Package exports three functions:

  • generateObjectTypeFromZod – takes a zod contract and returns Object type.
  • generateInputTypeFromZod – takes a zod contract and returns Input type.
  • registerZodEnumType – registers enumeration declared using zod (both z.enum() and z.nativeEnum()).

Important notes

Naming

The GraphQL schema became very powerful when types it consist have comprehensive and understandable names. To achieve that, we need a fully manageable way to deal with types naming. And this package insists that way – every single operation requires you to provide explicit name for type.

Declaration order

The order in which functions are called is important. You must start from the most nested type/enum and go through the least ones.

Types support and mapping

  • z.date() type maps to GraphQLISODateTime (from @nestjs/graphql).
  • z.string().uuid() type maps to GraphQLUUID (from graphql-scalars).
  • z.number().int() type maps to Int (from @nestjs/graphql).
  • z.any() and z.unknown() types maps to GraphQLJSON (from graphql-scalars).
  • z.record() type (with all the arguments) maps to GraphQLJSONObject (from graphql-scalars).

Basic usage

Imagine that you have the following contracts:

// contracts.ts

import { z } from 'zod'

export const AuthType = z.nativeEnum({
  EMAIL: 'EMAIL',
  PHONE: 'PHONE',
} as const)

export const CountryEntity = z.object({
  code: z.string(),
  name: z.string(),
})

export const UserEntity = z.object({
  id: z.string().uuid(),
  name: z.string().describe('User name.'),
  age: z.number().int().describe('User age.'),
  authType: AuthType,
  country: CountryContract,
  dataBin: z.any(),
  someAmorphousData: z.record(z.string(), z.unknown())
})

To transform them up to GraphQL types suitable for NestJS, you need to:

// auth.types.ts

import { AuthType, CountryEntity, UserEntity } from 'contracts.ts'
import { registerZodEnumType, generateObjectTypeFromZod } from 'zod-to-nestjs-graphql'

registerZodEnumType(AuthType, {
  name: 'AuthType',
});

export const User = generateObjectTypeFromZod(UserEntity, {
  name: 'User',
});

...then in your resolvers:

// auth.resolver.ts

import { User } from 'auth.types.ts'

@Resolver()
export class AuthResolver {
  @Query(() => User)
  me() {
    // ...
  }
}

And it will produce the following GraphQL schema:

enum AuthType {
    PHONE
    EMAIL
}

type Country {
    code: String!
    name: String!
}

type User {
    id: UUID!
  
    """User name."""
    name: String!

    """User age."""
    age: Int!

    authType: AuthType!

    country: Country!
    
    dataBin: GraphQLJSON!

    someAmorphousData: GraphQLJSONObject!
}

You don't need to worry about nested objects (like CountryEntity in UserEntity) because they're registered automatically (with auto-generated names).

GraphQL types auto-registration and name generation

Types auto-registration is enabled by default.

Let's take a case study based on the example above.

// auth.types.ts

// ...

export const User = generateObjectTypeFromZod(UserEntity, {
  name: 'User',
});

generateObjectTypeFromZod here registers two objects.

One is UserEntity which is transformed to GraphQL Object type with name User. Its name is explicitly supplied in the name option.

Another one is CountryEntity. It nested to UserEntity object, in country key. Corresponding GraphQL Object type will be named Country – the name is generated based on the key name, first transformed to PascalCase (in according to GraphQL naming recommendations).

A few more examples:

export const Person = z.object({
  country: CountryContract,           // Country
  personalDetails: DetailsContract,   // PersonalDetails
  misc_data: MiscContract             // MiscData
})

Advanced usage

Overriding auto-generated names of types

From the example above:

// auth.types.ts

// ...

export const User = generateObjectTypeFromZod(UserEntity, {
  name: 'User'
});

To override auto-generated name, register an object manually, before it happens automatically (before you will call generateObjectTypeFromZod):

// auth.types.ts

// Preliminary registering nested type to take full control of it.
export const Country = generateObjectTypeFromZod(CountryEntity, {
  name: 'MySpecialNameOfCountry',
  description: 'Country object.'
});

export const User = generateObjectTypeFromZod(UserEntity, {
  name: 'User'
});

Or, in cases if you don't use a newly registered GraphQL type anywhere in resolvers, there is one more way to do this:

// auth.types.ts

// ...

export const User = generateObjectTypeFromZod(
  UserEntity,
  { name: 'User' },
  {
    // The order of registration is still important. Register most nested types first.
    additionalRegistrations: [
      [
        Country,
        {
          name: 'MySpecialNameOfCountry',
          description: 'Country object.'
        }
      ],
    ],
  }
);

By this we get the same result as in the example above.

Hot replacements

Occasionally you will need to change the type on the fly. Let's take contracts with highly nested objects inside:

// contracts.ts

import { z } from 'zod'

export const NestedDataContract = z.object({
  data: z.object({}),
})

// A contract with highly nesting.
export const UserDataContract = z.object({
  // Property that have many nested objects inside.
  data: NestedDataContract,
})

export const UserEntity = z.object({
  id: z.string().uuid(),
  name: z.string().describe('User name.'),
  data: UserDataContract,
})

In case when you don't want to register all these nested types to GraphQL too, just replace it with more appropriate:

// auth.types.ts

import { AuthType, CountryEntity, UserEntity } from 'contracts.ts'
import { registerZodEnumType, generateObjectTypeFromZod } from 'zod-to-nestjs-graphql'

registerZodEnumType(AuthType, {
  name: 'AuthType',
});

export const User = generateObjectTypeFromZod(
  UserEntity,
  { name: 'User' },
  {
    hotReplacements: [
      {
        // Replace value of “data” in “UserDataContract” (“NestedDataContract”)
        origin: User.shape.data.shape.data,
        // ...with just “GraphQLJSONObject”.
        replacement: z.record(z.string(), z.unknown())
      },
    ],
  }
);

After this, the GraphQL schema would look like:

type User {
    id: UUID!
  
    """User name."""
    name: String!

    data: GraphQLJSONObject!
}