Spoosh
Core

Schema Definition

Define type-safe API schemas with TypeScript

Spoosh uses TypeScript types to define your API structure. This gives you full autocomplete and type checking for all API calls.

Basic Schema

Define your API schema as a nested type structure:

import type { Endpoint } from "@spoosh/core";

type User = {
  id: number;
  name: string;
  email: string;
};

type ApiSchema = {
  users: {
    $get: User[];  // Simple form - just the return type
    $post: Endpoint<User, { name: string; email: string }>;  // With body
  };
};

Endpoint Types

Simple Data Type

For endpoints that only return data (no body, query, or formData), you can use the type directly:

const {  } = await .users.$get();

Endpoint<TData>

For more explicit typing, use the Endpoint wrapper:

const {  } = await .users.$get();

Endpoint<TData, TBody>

Endpoint with a request body:

const {  } = await .users.$post({
  : { : "John", : "john@example.com" },
});

EndpointWithQuery<TData, TQuery>

Endpoint with query parameters:

const {  } = await .users.$get({
  : { : 1, : 10 },
});

EndpointWithFormData<TData, TFormData>

Endpoint that accepts form data:

const {  } = await .users.avatar.$post({
  : { :  },
});

EndpointDefinition<T>

For full control over all endpoint properties:

const {  } = await .users.$get({
  : { : 1, : 10 },
});

Dynamic Path Segments

Use _ to define dynamic path segments:

type ApiSchema = {
  users: {
    $get: Endpoint<User[]>;
    _: {
      $get: Endpoint<User>;
      $put: Endpoint<User, Partial<User>>;
      $delete: Endpoint<void>;
    };
  };
};

// Usage
const { data: users } = await api.users.$get();           // GET /users
const { data: user } = await api.users[123].$get();       // GET /users/123
const { data } = await api.users[123].$put({ body: {} }); // PUT /users/123
await api.users[123].$delete();                           // DELETE /users/123

Nested Dynamic Segments

type ApiSchema = {
  users: {
    _: {
      posts: {
        $get: Endpoint<Post[]>;
        _: {
          $get: Endpoint<Post>;
        };
      };
    };
  };
};

// Usage
await api.users[1].posts.$get();        // GET /users/1/posts
await api.users[1].posts[42].$get();    // GET /users/1/posts/42

Typed Dynamic Params

Use the function syntax for type-safe params:

// Function syntax (recommended for type safety)
const { data } = await api.users(":userId").$get({
  params: { userId: "123" },
});

// Bracket syntax (works but no type inference)
const { data } = await api.users[userId].$get();

HTTP Methods

Spoosh supports all common HTTP methods:

MethodTypeDescription
$getEndpointGET request
$postEndpointPOST request
$putEndpointPUT request
$patchEndpointPATCH request
$deleteEndpointDELETE request

Schema Organization

For larger APIs, organize your schema into separate files:

src/api/schema/users.ts
import type { Endpoint, EndpointWithQuery } from "@spoosh/core";

export type UsersSchema = {
  users: {
    $get: EndpointWithQuery<User[], { page?: number }>;
    $post: Endpoint<User, CreateUserBody>;
    _: {
      $get: Endpoint<User>;
      $put: Endpoint<User, UpdateUserBody>;
      $delete: Endpoint<void>;
    };
  };
};
src/api/schema/posts.ts
import type { Endpoint } from "@spoosh/core";

export type PostsSchema = {
  posts: {
    $get: Endpoint<Post[]>;
    $post: Endpoint<Post, CreatePostBody>;
  };
};
src/api/schema/index.ts
import type { UsersSchema } from "./users";
import type { PostsSchema } from "./posts";

export type ApiSchema = UsersSchema & PostsSchema;

Type Inference from Hono

If you're using Hono on the server, you can automatically infer your schema:

import type { HonoToSpoosh } from "@spoosh/hono";
import type { AppType } from "./server";

type ApiSchema = HonoToSpoosh<AppType>;

See the Hono Integration guide for details.

Summary

TypeDescriptionExample
TDataSimple data type (no body)$get: User[]
Endpoint<TData>Explicit endpoint$get: Endpoint<User[]>
Endpoint<TData, TBody>Endpoint with JSON body$post: Endpoint<User, CreateUserBody>
EndpointWithQuery<TData, TQuery>Endpoint with query params$get: EndpointWithQuery<User[], { page: number }>
EndpointWithFormData<TData, TForm>Endpoint with form data$post: EndpointWithFormData<Result, { file: File }>
EndpointDefinition<T>Full endpoint definition$get: EndpointDefinition<{ data: User[]; query: { page: number } }>
_Dynamic path segmentusers: { _: { $get: User } }

On this page