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 flat path-based type structure:

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

type ApiSchema = {
  users: {
    GET: { data: User[] };
    POST: { data: User; body: { name: string; email: string } };
  };
  "users/:id": {
    GET: { data: User };
    PUT: { data: User; body: Partial<User> };
    DELETE: void;
  };
};

Endpoint Types

{ data }

For endpoints that only return data:

const {  } = await ("users").GET();

{ data; body }

Endpoint with a request body:

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

{ data; query }

Endpoint with query parameters:

const {  } = await ("users").GET({
  : { : 1, : 10 },
});

{ data; body } with Files

Use the form() wrapper to send file uploads as multipart/form-data:

import {  } from "@spoosh/core";

const  = <>({ : "/api" });

const {  } = await ("users/avatar").POST({
  : ({ :  }),
});

Dynamic Path Segments

Use path parameters with :param syntax in your schema:

type ApiSchema = {
  users: {
    GET: { data: User[] };
  };
  "users/:id": {
    GET: { data: User };
    PUT: { data: User; body: Partial<User> };
    DELETE: void;
  };
};

// Usage with params
const { data: users } = await api("users").GET(); // GET /users
const { data: user } = await api("users/:id").GET({ params: { id: 123 } }); // GET /users/123
const { data } = await api("users/:id").PUT({ params: { id: 123 }, body: {} }); // PUT /users/123
await api("users/:id").DELETE({ params: { id: 123 } }); // DELETE /users/123

// With variables
const userId = 123;
await api("users/:id").GET({ params: { id: userId } });

Nested Dynamic Segments

type ApiSchema = {
  "users/:userId/posts": {
    GET: { data: Post[] };
  };
  "users/:userId/posts/:postId": {
    GET: { data: Post };
  };
};

// Usage
await api("users/:userId/posts").GET({ params: { userId: 1 } }); // GET /users/1/posts
await api("users/:userId/posts/:postId").GET({
  params: { userId: 1, postId: 42 },
}); // GET /users/1/posts/42

With React Hooks

Dynamic params work seamlessly with useRead and useWrite:

import { useRead, useWrite } from "./api/client";

function UserPostsList({ userId }: { userId: number }) {
  // useRead with params
  const { data: posts } = useRead((api) =>
    api("users/:userId/posts").GET({ params: { userId } })
  );

  // useWrite with POST
  const { trigger: createPost } = useWrite(
    (api) => api("users/:userId/posts").POST
  );

  // useWrite with DELETE and params
  const { trigger: deletePost } = useWrite(
    (api) => api("users/:userId/posts/:postId").DELETE
  );

  const handleCreatePost = async () => {
    await createPost({ params: { userId }, body: { title: "New Post" } });
  };

  const handleDeletePost = async (postId: number) => {
    await deletePost({ params: { userId, postId } });
  };

  return (
    <div>
      <button onClick={handleCreatePost}>Create Post</button>
      {posts?.map((post) => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <button onClick={() => handleDeletePost(post.id)}>Delete</button>
        </div>
      ))}
    </div>
  );
}

HTTP Methods

Spoosh supports all common HTTP methods:

MethodDescription
GETGET request
POSTPOST request
PUTPUT request
PATCHPATCH request
DELETEDELETE request

Schema Organization

For larger APIs, organize your schema into separate files:

src/api/schema/users.ts
export type UsersSchema = {
  users: {
    GET: { data: User[]; query: { page?: number } };
    POST: { data: User; body: CreateUserBody };
  };
  "users/:id": {
    GET: { data: User };
    PUT: { data: User; body: UpdateUserBody };
    DELETE: void;
  };
};
src/api/schema/posts.ts
export type PostsSchema = {
  posts: {
    GET: { data: Post[] };
    POST: { data: Post; body: CreatePostBody };
  };
};
src/api/schema/index.ts
import type { UsersSchema } from "./users";
import type { PostsSchema } from "./posts";

export type ApiSchema = UsersSchema & PostsSchema;

Type Inference from Server Frameworks

Hono

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

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

type ApiSchema = HonoToSpoosh<ReturnType<typeof hc<AppType>>>;

See the Hono guide for details.

Elysia

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

import type { ElysiaToSpoosh } from "@spoosh/elysia";
import type { App } from "./server";

type ApiSchema = ElysiaToSpoosh<App>;

See the Elysia guide for details.

Summary

TypeDescriptionExample
{ data }Endpoint with data onlyGET: { data: User[] }
{ data; body }Endpoint with JSON bodyPOST: { data: User; body: CreateUserBody }
{ data; query }Endpoint with query paramsGET: { data: User[]; query: { page: number } }
{ data; error }Endpoint with typed errorGET: { data: User; error: ApiError }
voidNo response bodyDELETE: void
"path/:param"Dynamic path segment"users/:id": { GET: { data: User } }

On this page