Spoosh
Getting Started

First API Call

Make your first type-safe API call with Spoosh

This guide walks through making different types of API calls with Spoosh.

Reading Data

Use useRead to fetch data:

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

function UserProfile({ userId }: { userId: number }) {
  const { data, loading, error } = useRead(
    (api) => api.users[userId].$get()
  );

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;

  return (
    <div>
      <h1>{data?.name}</h1>
      <p>{data?.email}</p>
    </div>
  );
}

With Query Parameters

function SearchUsers({ query }: { query: string }) {
  const { data } = useRead(
    (api) => api.search.$get({ query: { q: query, page: 1 } })
  );

  return (
    <ul>
      {data?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Conditional Fetching

function UserProfile({ userId }: { userId: number | null }) {
  const { data, loading } = useRead(
    (api) => api.users[userId!].$get(),
    { enabled: userId !== null }
  );

  // ...
}

Writing Data

Use useWrite for mutations (POST, PUT, DELETE):

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

function CreateUserForm() {
  const { trigger, loading, error } = useWrite(
    (api) => api.users.$post
  );

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);

    const result = await trigger({
      body: {
        name: formData.get("name") as string,
        email: formData.get("email") as string,
      },
    });

    if (result.data) {
      console.log("Created user:", result.data);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit" disabled={loading}>
        {loading ? "Creating..." : "Create User"}
      </button>
      {error && <p className="error">{error.message}</p>}
    </form>
  );
}

Update and Delete

function UserActions({ userId }: { userId: number }) {
  const { trigger: updateUser } = useWrite(
    (api) => api.users[userId].$put
  );

  const { trigger: deleteUser } = useWrite(
    (api) => api.users[userId].$delete
  );

  const handleUpdate = async () => {
    await updateUser({ body: { name: "Updated Name" } });
  };

  const handleDelete = async () => {
    await deleteUser();
  };

  return (
    <div>
      <button onClick={handleUpdate}>Update</button>
      <button onClick={handleDelete}>Delete</button>
    </div>
  );
}

Dynamic Path Parameters

Spoosh supports multiple syntaxes for dynamic path segments:

// Bracket syntax with literal value
const { data } = useRead((api) => api.users[123].$get());

// Function syntax with typed params (recommended)
const { data } = useRead(
  (api) => api.users(":userId").$get({ params: { userId: "123" } })
);

// Variable in brackets
const userId = 123;
const { data } = useRead((api) => api.users[userId].$get());

The function syntax (":userId") provides the best type inference for params.

Response Format

All API calls return a SpooshResponse:

type SpooshResponse<TData, TError> = {
  status: number;       // HTTP status code
  data: TData | undefined;    // Response data (if successful)
  error: TError | undefined;  // Error data (if failed)
  headers?: Headers;    // Response headers
  aborted?: boolean;    // True if request was aborted
};

Next Steps

On this page