Concept

In the core, there is a simple idea. You can define methods and types.

Methods

Method is an endpoint any user request starts from. It has input and output, described with types.

Method has a name. It can contain dots, which helps to organize methods into scopes. E.g. artciles.create, users.update, etc.

To define a method use api.createMethod function.

api.createMethod(
    // name of the method
    'currentUser.update',
    
    // input data shape
    object({ 
        firstName: optional(string()),
        lastName: optional(string()),
    }),
    
    // output data shape
    boolean(),
    
    // handler
    async (input) => { /*...*/ },
);

What does apizy do here:

  • Makes sure your handler input and output matches defined data shape
    • On compilation state, using agile typescript typings
    • In runtime, using checks
  • Creates internal type structure. Based on that it can:
    • generate documentation reference page
    • generate well typed typescript SDK, ready to be used on a frontend

Types

Types define a shape for input and output data of methods.

Sometimes it can be defined directly when defining methods (for simple cases).

api.createMethod(
    'currentUser.update',
    object({
        firstName: string(),
        lastName: string(),
    }),
    boolean(),
    async (input) => { /*...*/ },
);

You can store types in variable, to use it in different places

const $UserData = object({
    firstName: string(),
    lastName: string(),
});

api.createMethod(
    'currentUser.update',
    $UserData,
    boolean(),
    async (input) => { /*...*/ },
);

api.createMethod(
    'users.update',
    object({
        id: uuid(),
        data: $UserData,
    }),
    boolean(),
    async (input) => { /*...*/ },
);

Aliases

If you want to give your type some name, visible in documentation and in SDK typings, you can use api.createAlias

const $UserData = api.createAlias('UserData', object({
    firstName: string(),
    lastName: string(),
}));

api.createMethod(
    'currentUser.update',
    $UserData,
    boolean(),
    async (input) => { /*...*/ },
);

api.createMethod(
    'users.update',
    object({
        id: uuid(),
        data: $UserData,
    }),
    boolean(),
    async (input) => { /*...*/ },
);

Entities

In the real word usually you have data interfaces on your backend, responsible for entities. (Like User, Order, Product, etc).

You can’t just “throw” them into the API, because they can contain unserializable, secret, or just unnecessary data.

But it’d be cool, to have some kind of “wrappers” above these interfaces to serialize them to a client in simple and elegant way.

Here entities comes.

Let’s define an entities for User and Article.

// Interfaces you have in your project
interface User {
    id: string;
    nickname: string;
}

interface Article {
    id: string;
    authorId: string;
    content: string;
    tags: string[];
}

// Define API entities
const $User = api.createEntity<User>('User'); 
const $Article = api.createEntity<Article>('Article');

// Add resolvers for your entities
// They define, how your data is represented in an API, and how it should be serialized
$User.addResolver(
    {
        id: uuid(),
        nickname: string(),
    },
    user => ({
        id: user.id,
        nickname: user.nickname,
    })
);

$Article.addResolver(
    {
        id: uuid(),
        content: string(),
        tags: arrayOf(string()),
        author: extend($User),
          // extend() helper is used here, which means "author" property will not be included by default
          // only if client asked about it
    },
    article => ({
        id: article.id,
        content: article.content,
        tags: article.tags,
        author: async () => {
            // This method will be called only if needed
            const user = await MyDB.loadUser(article.authorId);
            
            // apizy knows that we should return $User here
            // so resolver of $User will be used automatically, to wrap this object
            return user;
        },
    })
);

// Define a method to retrive top 5 articles

api.createMethod(
    'articles.top',
    object({
        limit: int(),
    }),                // we don't expect input this time
    arrayOf($Article),   // array of articles should be returned
    async ({limit}) => {
        const articles = await MyDB.loadTopArticles({limit});
        
        // As apizy knows, that arrayOf($Article) should be returned
        // we just return array of articles.
        // Each article will be processes with $Article resolver
        return articles;
    }, 
);

When you generate a frontend SDK, it will have sdk.articles.top method.

await sdk.articles.top({limit: 5})
// Returns:
// {
//     id: '...',
//     content: '...',
//     tags: ['...', ...],
// }

await sdk.articles.top({limit: 5}, {extend: {author: {}}})
// Returns:
// {
//     id: '...',
//     content: '...',
//     tags: ['...', ...],
//     author: {
//        id: '...',
//        nickname: '...',
//     }
// }

Copyright © 2025 ototak.net Roman Ditchuk