Zodios

repository·main·Indexed 24 days ago

https://github.com/ecyrbe/zodios

A TypeScript-first API client and optional server framework that leverages Axios for HTTP requests and Zod for schema validation. It provides end-to-end type safety, autocompletion for URLs and parameters, and automatic response validation. The ecosystem includes specialized packages such as @zodios/express for REST APIs, @zodios/react and @zodios/solid for TanStack Query wrappers, and openapi-zod-client for generating clients from OpenAPI specifications.

Tokens
30.8K
Snippets
68
Records
121
Agent score
83%

What's inside Zodios

  1. What is Zodios?

    main
    Zodios is a REST API toolbox designed for end-to-end typesafety. It provides a clean, intuitive, and declarative syntax for creating REST APIs. While optimized for TypeScript to provide full autocompletion and typesafety, it is also compatible with pure JavaScript.
  2. Explore the Zodios Ecosystem

    main

    Zodios provides several specialized packages to extend its functionality:

    • openapi-zod-client: Generate a Zodios client from an OpenAPI specification.
    • @zodios/express: Provides full end-to-end type safety for REST APIs (similar to tRPC).
    • @zodios/plugins: A collection of plugins for Zodios.
    • @zodios/react: A @tanstack/react-query wrapper for Zodios.
    • @zodios/solid: A @tanstack/solid-query wrapper for Zodios.
  3. What is Zodios Context?

    main
    Zodios Context allows you to declare a typed context object that is available in all your Zodios handlers. This is useful for accessing data that is typically attached to the request object in Express apps (such as req.user) while ensuring that the data is properly typed throughout your handlers using Zod schemas.
  4. Understand plugin execution order

    main

    The execution order of Zodios plugins follows these rules:

    1. Global plugins (not attached to an endpoint) execute first.
    2. Endpoint-specific plugins execute next.
    3. Request Interceptors: Executed in the order they were declared.
    4. Response Interceptors: Executed in reverse order of their declaration.

    Example flow for a request and response:

    • Request: Global Plugin $\rightarrow$ Endpoint Plugin $\rightarrow$ Specific Plugin
    • Response: Specific Plugin $\rightarrow$ Endpoint Plugin $\rightarrow$ Global Plugin
    apiClient.use("getUser", pluginLog('2'));
    apiClient.use(pluginLog('1'));
    apiClient.use("get", "/users/:id", pluginLog('3'));
    
    apiClient.get("/users/:id", { params: { id: 7 } });
    
    // output:
    // request 1 
    // request 2 
    // request 3 
    // response 3
    // response 2
    // response 1
  5. Use Alias Hooks for Endpoints

    main

    If you define an alias in your API definition, Zodios generates specialized hooks for those endpoints. This provides full auto-completion and automatic key management.

    Query Aliases

    Used for GET requests. Returns a QueryResult containing the response data, all standard react-query properties, the generated key, and an invalidate helper.

    // Example: identical to hooks.useQuery("/users")
    const { data: users, isLoading, isError, invalidate, key } = hooks.useGetUsers();

    Immutable Query Aliases

    Used for POST requests that act as queries. These are only available if you set immutable: true in your API definition.

    // Example: identical to hooks.useImmutableQuery("/users/search")
    const { data: users, isLoading, isError } = hooks.useSearchUsers({ name: "John" });

    Mutation Aliases

    Used for POST, PUT, PATCH, or DELETE endpoints.

    // Example: identical to usePost("/users")
    const { mutate } = hooks.useCreateUser();
  6. Zodios Package Ecosystem

    main

    Zodios is modular. You can use frontend and backend packages independently by sharing the API definition between teams. The ecosystem includes:

    • @zodios/core: The core library containing the typesafe API client. Can be used standalone.
    • @zodios/plugins: A collection of plugins for the API client.
    • @zodios/react: React hooks for the client, built on top of tanstack-query.
    • @zodios/solid: Solid hooks for the client, built on top of tanstack-query.
    • @zodios/express: A typesafe adapter for Express.
    • @zodios/openapi: Helpers to generate OpenAPI specs and Swagger UI from Zodios API definitions.
  7. Use endpoint aliases for type-safe requests

    main

    If you define an alias in your API definition, you can call that alias directly on the Zodios instance instead of using generic HTTP methods. This provides the best developer experience and type safety.

    Query Aliases (GET, etc.)

    Used for endpoints that do not require a body. function [alias](config?: ZodiosRequestOptions): Promise<Response>;

    • Use params to pass path parameters.
    • Use queries to pass query parameters.

    Mutation Aliases (POST, PUT, PATCH, DELETE)

    Used for endpoints that require a body. function [alias](body: BodyParam, config?: ZodiosRequestOptions): Promise<Response>;

  8. Define a Zodios API definition

    main

    A Zodios API definition is a centralized JavaScript array of endpoint descriptions used to declare your REST API endpoints. This object is designed to be shared between your server and client code to ensure type safety and consistency. If you do not control the API server, you can still use an API definition solely for your client-side Zodios instance.

    Each endpoint in the array is an object describing the HTTP method, path, and response schema.

  9. Use Zodios with Solid.js and TanStack Query

    main

    Zodios provides a ZodiosHooks class that integrates your Zodios API client with @tanstack/solid-query. This allows you to use TanStack Query's powerful data fetching patterns (like infinite queries and mutations) while maintaining full type safety from your Zod schema definitions.

    To use it:

    1. Define your schemas using zod.
    2. Define your API using makeApi.
    3. Instantiate Zodios with your base URL and API definition.
    4. Instantiate ZodiosHooks passing a key and your Zodios instance.
    5. Wrap your application in a QueryClientProvider from @tanstack/solid-query.
    6. Use the methods on the ZodiosHooks instance (e.g., createInfiniteQuery, createMutation, or alias-based methods like createCreateUser) within your Solid components.
    import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
    import { makeApi, Zodios } from "@zodios/core";
    import { ZodiosHooks } from "../src";
    import { z } from "zod";
    
    // 1. Define Schemas
    const userSchema = z.object({ id: z.number(), name: z.string() }).required();
    const usersSchema = z.array(userSchema);
    
    // 2. Define API
    const api = makeApi([
      {
        method: "get",
        path: "/users",
        alias: "getUsers",
        response: usersSchema,
      },
      {
        method: "post",
        path: "/users",
        alias: "createUser",
        parameters: [{ name: "body", type: "Body", schema: z.object({ name: z.string() }).required() }],
        response: userSchema,
      },
    ]);
    
    // 3. Setup Zodios and Hooks
    const zodios = new Zodios("https://api.example.com", api);
    const zodiosHooks = new ZodiosHooks("api-key", zodios);
    
    // 4. Use in Component
    const Users = () => {
      const users = zodiosHooks.createGetUsers({ params: { limit: 10 } });
      const userMutation = zodiosHooks.createCreateUser(undefined, {
        onSuccess: () => users.invalidate(),
      });
    
      return (
        <button onClick={() => userMutation.mutate({ name: "john" })}>Create</button>
      );
    };
    
    // 5. Provide QueryClient
    const queryClient = new QueryClient();
    export const App = () => (
      <QueryClientProvider client={queryClient}>
        <Users />
      </QueryClientProvider>
    );
  10. Send multipart/form-data requests

    main

    Zodios supports multipart/form-data using the requestFormat: "form-data" option.

    Node.js Users: You must install the form-data package and polyfill globalThis.FormData before importing Zodios: globalThis.FormData = require("form-data");.

    Alternatively, you can use your own multipart library (like form-data on Node) by defining the parameter schema as z.instanceof(FormData) and passing the headers manually.

    // Option 1: Using integrated requestFormat
    const apiClient = new Zodios(
      "https://mywebsite.com",
      [{ 
        method: "post",
        path: "/upload",
        alias: "upload",
        description: "Upload a file",
        requestFormat: "form-data",
        parameters:[
          {
            name: "body",
            type: "Body",
            schema: z.object({
              file: z.instanceof(File),
            }),
          }
        ],
        response: z.object({
          id: z.number(),
        }),
      }],
    );
    const id = await apiClient.upload({ file: document.querySelector('#file').files[0] });
    // Option 2: Using custom FormData library (e.g. on Node)
    import FormData from 'form-data';
    
    const apiClient = new Zodios(
      "https://mywebsite.com",
      [{ 
        method: "post",
        path: "/upload",
        alias: "upload",
        description: "Upload a file",
        parameters:[
          {
            name: "body",
            type: "Body",
            schema: z.instanceof(FormData),
          }
        ],
        response: z.object({
          id: z.number(),
        }),
      }],
    );
    const form = new FormData();
    form.append('file', document.querySelector('#file').files[0]);
    const id = await apiClient.upload(form, { headers: form.getHeaders() });