Skip to content

Types

Types are used everywhere in this framework, from defining parameters to response formats.

There are 2 API's to define types in itty-router-open, but we recommend that you choose one and stick to it.

  • Native types
  • Zod types

Native types

Native types, are referred in this documentation as 3 things that work interchangeably:

  • itty-router-openapi types (Email, Ipv4, ...)
  • JavaScript native types (String, Number, ...)
  • JavaScript variables ('myvar' gets parsed as String, and so on for numbers and bools)

When these types were defined we tried to make them as simple as possible, tried to add as much compatibility as possible. This is the main reason we support such wide variations.

itty-router-openapi types

itty-router-openapi types are imported as the following:

import { Email, Str } from '@cloudflare/itty-router-openapi'

Here is a list of all available itty-router-openapi types and the parameters available

Name Arguments
Num description example default required
Int description example default required
Str description example default format required
Enumeration description example default values enumCaseSensitive required
DateTime description example default required
DateOnly description example default required
Bool description example default required
Regex description example default pattern patternError required
Email description example default required
Uuid description example default required
Hostname description example default required
Ipv4 description example default required
Ipv6 description example default required

All itty-router-openapi types can be instantiated in 3 ways:

  • normal class (new Email({description: 'user email'}))
  • raw class (just Email and itty-router-openapi handles the instantiation)
  • instantiation by parameter location (Query(Str, { description: 'Task description' }))

As an example, you could define the same query parameter in these 3 ways:

import { Query, Str } from '@cloudflare/itty-router-openapi'

const queryDescription1 = Query(Str)
const queryDescription2 = Query(new Str({ description: 'Task description' }))
const queryDescription3 = Query(Str, { description: 'Task description' })

Then use it in a endpoint like this:

import { Query, Str, Int, OpenAPIRoute } from '@cloudflare/itty-router-openapi'

export class Example extends OpenAPIRoute {
  static schema = {
    parameters: {
      description: queryDescription,
    },
    responses: {
      '200': {
        schema: {},
      },
    },
  }

  async handle(request: Request, env: any, context: any, data: any) {
    return {
      validatedDescription: data.query.description
    }
  }
}

JavaScript native types

JavaScript native types can be used everywhere the itty-router-openapi types are used, sometimes it just makes defining schemas simpler

For example here is the same query parameters as above defined as JavaScript native types. In this case you cannot instantiate the type with the new keyword, so you must pass the parameters to the parameter location.

import { Query } from '@cloudflare/itty-router-openapi'

const queryDescription1 = Query(String)
const queryDescription3 = Query(String, { description: 'Task description' })

JavaScript variables

The main reason we support JavaScript variables is to make it as simple as possible to define schemas and even set as example.

Here is an example of the same end result defined with a variable and as itty-router-openapi type.

import { Query } from '@cloudflare/itty-router-openapi'

const queryDescription1 = Query('john cena')
const queryDescription3 = Query(String, { example: 'john cena' })

Defining request bodies and response schemas is even easier with variables, here is another end result defined as variables and with itty-router-openapi type:

export class ExampleVariables extends OpenAPIRoute {
  static schema = {
    requestBody: {
      name: 'john cena',
      description: 'John Felix Anthony Cena is an American professional wrestler.',
      height: 1.85,
      can_be_seen: false,
    }
  }

  async handle(
    request: Request,
    env: any,
    context: any,
    data: any
  ) {
    // ...
  }
}
export class ExampleIttyRouterOpenAPI extends OpenAPIRoute {
  static schema = {
    requestBody: {
      name: new Str({ example: 'john cena' }),
      description: new Str({ example: 'John Felix Anthony Cena is an American professional wrestler.' }),
      height: new Num({ example: 1.85 }),
      can_be_seen: new Bool({ example: false }),
    }
  }

  async handle(
    request: Request,
    env: any,
    context: any,
    data: any
  ) {
    // ...
  }
}

Defining response bodies is such an easy task with variables, you literaly just have to call your endpoint, and copy-paste the response is your schema.

export class ExampleRequestBody extends OpenAPIRoute {
  static schema = {
    responses: {
      '200': {
        description: 'Successful Response',
        schema: {
          'asn': {
            'name': 'CLOUDFLARENET',
            'nameLong': '',
            'aka': 'Cloudflare',
            'asn': 13335,
            'website': 'https://www.cloudflare.com',
            'country': 'US',
            'countryName': 'United States',
            'orgName': 'Cloudflare, Inc.',
            'related': [
              {
                'name': 'CLOUDFLARENET-AUS',
                'aka': '',
                'asn': 14789,
                'estimatedUsers': null,
              },
              {
                'name': 'CLOUDFLARENET-SFO',
                'aka': '',
                'asn': 394536,
                'estimatedUsers': null,
              },
              {
                'name': 'CLOUDFLARENET-SFO05',
                'aka': '',
                'asn': 395747,
                'estimatedUsers': null,
              },
            ],
          },
        },
      },
    },
  }

  async handle(
    request: Request,
    env: any,
    context: any,
    data: any,
  ) {
    // ...
  }
}

Zod types

Zod types, are as the name says, types from the Zod library, they are much more verbose than the native types, but allows you to define almost everything supported by the OpenAPI specification, like AnyOf, AllOF, etc.

Zod is a TypeScript-first schema declaration and validation library. I'm using the term "schema" to broadly refer to any data type, from a simple string to a complex nested object.

Zod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures.

Zod types can be used everywhere the Native types are used (because native types are actually just Zod wrappers).

Zod allows you to have a much more granular control over what is a valid input or not.

For example you could define a Query parameter that only accepts number bellow or equal to 10 with the following line.

import { Query } from '@cloudflare/itty-router-openapi'
import { z } from 'zod'

const queryResponseLimit = Query(z.coerce.number().lte(10))

Then use it in a endpoint like this:

import { OpenAPIRoute } from '@cloudflare/itty-router-openapi'

export class Example extends OpenAPIRoute {
  static schema = {
    parameters: {
      limit: queryResponseLimit,
    },
    responses: {
      '200': {
        schema: {},
      },
    },
  }

  async handle(request: Request, env: any, context: any, data: any) {
    return {
      validatedLimit: data.query.limit
    }
  }
}

Read the Zod documentation here!

Generating Zod types for your requests

Often, you need to retrieve the request body while maintaining type definitions for that body. If you are using Zod for type validation, this becomes very straightforward!

Here's how you can create a TypeScript type for your Zod schema:

const Task = z.object({
  name: z.string(),
  description: z.string().or(z.string().array()),
  steps: z.string().array(),
});
type MyTaskType = z.infer<typeof Task>;

Now, you can simply execute the following:

const body = request.json<MyTaskType>();

Your body is now correctly typed!