type-system

apizy type-system is used to describe types of your API data structures. Input and output.

In short, how does it work

An apizy type-system is based on special helpers: javaScript functions that define types.

It allows apizy to extract appropriate types from your definitions on compilation stage, as well as do runtime checks of entire input and output when running server and also generate typescript definitions for SDK.

E.g.

import {createApi, object, float, arrayOf, optional, string, nullable, uuid, boolean} from 'apizy';
const api = createApi();

api.createEntity(
    'methodName',
    object({
        a: float(),
        b: optional(string()),
        c: nullable(arrayOf(object({
            id: uuid(),
        }))),
    }),
    boolean(),
    async (input) => {
        // input has type {
        //      a: number;
        //      b?: string;
        //      c: Array<{
        //          id: string;
        //      }> | null;
        // }
    }
);

Helpers

Primitive types

string

string()

int

int()

float

float()

boolean

boolean()

uuid

uuid()

oneOf

For fields that must match one of several values, use the oneOf helper.

const userRole = oneOf(['admin', 'editor', 'subscriber']);

const userShape = object({
    id: uuid(),
    role: userRole,
});

This ensures the role field only accepts one of the predefined values.

Structures

object

Defines an object that uses multiple types for its fields,

object({
    id: uuid(),
    email: string(),
    privateData: object({
        firstName: string(),
        lastName: string(),
    }),
});

arrayOf

const tagsShape = arrayOf(string());

const blogPostShape = object({
    title: string(),
    tags: tagsShape,
});

This ensures tags is an array of strings.

Modifiers

nullable

When a field can be null, use the nullable helper.

const userShape = object({
    id: uuid(),
    phoneNumber: nullable(string()),
});

phoneNumber can either be a string or null.

optional

To allow a field to be optional (can be omitted in the input), use the optional helper.

const userShape = object({
    id: uuid(),
    nickname: optional(string()),
});

Here nickname may or may not be included in the object.

defaultValue

The same as optional, but allows to set default value.

const userShape = object({
    role: defaultValue('user', string()),
});

Here if the role field is not provided, it defaults to 'user'.

Relations

extend

This helper allows you to add parts of output, which should not be loaded by default. But it can be loaded (extended) only if a client app requested that.

You can do that, because of performance reason, or size of the object.

Example

import {createApi, object, float, arrayOf, optional, string, nullable, uuid, boolean} from 'apizy';
const api = createApi();

api.createEntity(
    'users.getById',
    string(),
    object({
        id: uuid(),
        name: string(),
        topArticles: extend(arrayOf(object({  // <-- extend helpers here
            id: uuid(),
            text: string(),
        }))),
    }),
    async (input) => {
        return {
            id: '...',
            name: '...',
            topArticles: async () => {
                // this function will be called to load articles,
                // only when user request this extension
                return [ {
                    id: '...',
                    text: '...',
                } /* ,...*/ ];
            },
        };
    }
);

On client side:

sdk.users.getById('...')
// Returns: Promise<{
//     id: string;
//     name: string;
// }>
sdk.users.getById('...', {
    extend: {
        topArticles: {},
    },
});
// Returns: Promise<{
//     id: string;
//     name: string;
//     topArticles: {
//         id: string;
//         text: string;
//     },
// }>

Extends can be nested.

relation

TODO

Check out Entities first.


Copyright © 2025 ototak.net Roman Ditchuk