Zimic Documentation

website·Indexed 32 days ago

https://zimic.dev

A suite of type-safe utilities for handling and mocking HTTP requests. Includes @zimic/http for type-safe headers and search params, @zimic/fetch for typed requests and responses, and @zimic/interceptor for mocking HTTP requests in development and testing via local and remote interceptors with OpenAPI type generation support.

Tokens
33.7K
Snippets
174
Records
204
Agent score
60%

What's inside Zimic

  1. Overview of local HTTP interceptors

    Local HTTP interceptors in @zimic/interceptor allow developers to handle HTTP requests and return custom responses within the same process as the application using MSW (Mock Service Worker). They are primarily used for mocking backends during development or in unit/integration tests (e.g., with Jest or Vitest) to simulate success and error scenarios without requiring a real server or complex seed data.
  2. Overview of the Interceptor Server

    The interceptor server is a development and testing tool used to handle requests and return mock responses in coordination with remote interceptors. Communication between the server and remote interceptors is handled via Remote Procedure Calls (RPC) over WebSockets. This server should not be used as a production application server.
  3. Overview of @zimic/fetch API client

    @zimic/fetch is a minimal (~2 kB minified gzipped), type-safe, fetch-like API client with zero external dependencies. It allows developers to create fully typed requests and responses by utilizing @zimic/http schemas. Key features include the ability to define default options (such as base URL, headers, and search parameters) and the use of onRequest and onResponse listeners to inspect or modify network traffic.
  4. Overview of @zimic/http type-safe HTTP utilities

    @zimic/http is a collection of type-safe utilities designed to handle HTTP requests and responses. It provides tools for defining HTTP schemas to type endpoints, generating schemas from OpenAPI documentation via a CLI, and creating type-safe versions of native web objects including Headers, URLSearchParams, and FormData.
  5. Overview of @zimic/interceptor for HTTP mocking

    @zimic/interceptor is a type-safe interceptor library used for handling and mocking HTTP requests during development and testing. It allows developers to simulate success, loading, and error states for external services using @zimic/http schemas. It achieves network-level interception by combining MSW (Mock Service Worker) and interceptor servers, making mocked responses indistinguishable from real ones to the application. It also supports declarative assertions to verify that the application is making the expected requests.
  6. Overview of the Zimic ecosystem

    Zimic is a collection of TypeScript-first HTTP integration libraries that provide type-safe utilities for making, receiving, and mocking HTTP requests and responses. Its core philosophy is 'Typed by default,' utilizing centralized schemas as a single source of truth for HTTP endpoint structures. This eliminates the need for manual type casting (e.g., using as) and reduces runtime errors caused by API changes or typos in paths and parameters.
  7. Manage HTTP request handlers with HttpRequestHandler

    The HttpRequestHandler is used to declare responses for intercepted requests. If multiple handlers within the same interceptor match the same HTTP method and path, the last handler created is the one used.

    Important for Remote Interceptors: When using a remote interceptor, you must await the handler before making requests. Remote operations are asynchronous and communicate with the interceptor server. HttpRequestHandler automatically batches changes, so a single await at the end of a chain is sufficient.

    // For remote interceptors, await the chain to ensure declarations are committed
    const handler = await interceptor
      .patch('/users/:userId')
      .with({ body: { username: 'new-username' } })
      .respond({ status: 204 });
  8. Handle unhandled HTTP requests in @zimic/interceptor

    When a request is not matched by any interceptor handler, it is considered 'unhandled'. By default, these requests are rejected and logged to the console.

    Behavior by Interceptor Type:

    • Local Interceptors: Can either bypass (allow the request to reach the real network) or reject (fail with a network error). The default is to reject.
    • Remote Interceptors: Always reject unhandled requests because they have already reached the interceptor server and cannot be bypassed.

    Resolution Logic: If a request is unhandled, the library looks for a running interceptor whose baseURL is a prefix of the request URL. If multiple match, the last interceptor started via interceptor.start() is used. If no running interceptor matches, the request is rejected with a network error.

  9. Define and use request headers in Zimic interceptors

    To use request headers in intercepted requests, first declare their types in the HttpSchema. This provides type safety and autocompletion when accessing headers in handler.with(), handler.respond(), and handler.requests.

    Request headers are accessed via the .get() method on the headers object of the request.

    import { HttpSchema } from '@zimic/http';
    
    type Schema = HttpSchema<{ 
      '/users': { 
        GET: { 
          request: { 
            headers: { 
              authorization: string; 
              'accept-language'?: string; 
            }; 
          }; 
          response: { 200: { body: any[] } }; 
        }; 
      }; 
    }>;
    
    const handler = interceptor
      .get('/users')
      .with({
        headers: {
          authorization: 'Bearer my-token',
          'accept-language': 'en',
        },
      })
      .respond((request) => {
        console.log(request.headers.get('authorization'));
        return { status: 200, body: users };
      })
      .times(1);
    
    // Accessing headers from captured requests
    console.log(handler.requests[0].headers.get('authorization'));
  10. Send binary data or streams in request bodies

    Binary bodies can be declared as Blob, ArrayBuffer, or ReadableStream. Use ReadableStream specifically for large files or chunked data to enable request streaming.
    import { HttpSchema } from '@zimic/http';
    
    type Schema = HttpSchema<{
      '/upload': {
        POST: {
          request: {
            headers?: { 'content-type'?: string };
            body: ReadableStream; // Use Blob or ArrayBuffer for non-streamed binary
          };
        };
        response: {
          201: { body: { id: string, url: string } };
        };
      };
    }>;
    
    // Streaming a file from the file system
    import fs from 'fs';
    import { Readable } from 'stream';
    
    const fileStream = fs.createReadStream('video.mp4');
    const requestStream = Readable.toWeb(fileStream);
    
    interceptor
      .post('/upload')
      .with({
        body: requestStream,
      })
      .respond({
        status: 201,
        body: video,
      })
      .times(1);
  11. Meet the environment requirements for @zimic/http

    @zimic/http works in any client-side or server-side environment that supports the Fetch API. For TypeScript users, version 5.0 or higher is required. The following tsconfig.json configuration is recommended to ensure compatibility with the library's type system.
    {
      "compilerOptions": {
        "strict": true,
        "lib": ["esnext", "dom", "dom.iterable"]
      }
    }
  12. Handle URL-encoded response bodies

    To receive a URL-encoded response body, use the HttpSearchParams<T> type in your schema. Use response.formData() to parse the response into a typed object.
    import { HttpSchema, HttpSearchParams } from '@zimic/http';
    import { createFetch } from '@zimic/fetch';
    
    type Schema = HttpSchema<{
      '/users': {
        GET: {
          response: {
            200: {
              headers?: { 'content-type'?: 'application/x-www-form-urlencoded' };
              body: HttpSearchParams<{ username: string }>;
            };
          };
        };
      };
    }>;
    
    const fetch = createFetch<Schema>({ baseURL: 'http://localhost:3000' });
    const response = await fetch('/users', { method: 'GET' });
    const searchParams = await response.formData();