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 injectRead to fetch data:

import { Component, input } from "@angular/core";
import { injectRead } from "../api/client";

@Component({
  selector: "app-user-profile",
  template: `
    @if (user.loading()) {
      <app-spinner />
    } @else if (user.error()) {
      <app-error-message [error]="user.error()!" />
    } @else {
      <div>
        <h1>{{ user.data()?.name }}</h1>
        <p>{{ user.data()?.email }}</p>
      </div>
    }
  `,
})
export class UserProfileComponent {
  userId = input.required<number>();

  user = injectRead((api) =>
    api("users/:id").GET({ params: { id: this.userId() } })
  );
}

With Query Parameters

import { Component, input } from "@angular/core";
import { injectRead } from "../api/client";

@Component({
  selector: "app-search-users",
  template: `
    <ul>
      @for (user of searchResults.data(); track user.id) {
        <li>{{ user.name }}</li>
      }
    </ul>
  `,
})
export class SearchUsersComponent {
  searchTerm = input.required<string>();

  searchResults = injectRead((api) =>
    api("search").GET({ query: { q: this.searchTerm(), page: 1 } })
  );
}

Conditional Fetching

import { Component, input } from "@angular/core";
import { injectRead } from "../api/client";

@Component({
  selector: "app-user-profile",
  template: `...`,
})
export class UserProfileComponent {
  userId = input<number | null>(null);

  user = injectRead(
    (api) => api("users/:id").GET({ params: { id: this.userId()! } }),
    {
      enabled: () => this.userId() !== null,
    }
  );
}

Writing Data

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

import { Component } from "@angular/core";
import { injectWrite } from "../api/client";

@Component({
  selector: "app-create-user-form",
  template: `
    <form (ngSubmit)="handleSubmit($event)">
      <input name="name" placeholder="Name" required #nameInput />
      <input
        name="email"
        type="email"
        placeholder="Email"
        required
        #emailInput
      />
      <button type="submit" [disabled]="createUser.loading()">
        {{ createUser.loading() ? "Creating..." : "Create User" }}
      </button>
      @if (createUser.error()) {
        <p class="error">{{ createUser.error()!.message }}</p>
      }
    </form>
  `,
})
export class CreateUserFormComponent {
  createUser = injectWrite((api) => api("users").POST);

  async handleSubmit(event: SubmitEvent) {
    event.preventDefault();
    const form = event.target as HTMLFormElement;
    const formData = new FormData(form);

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

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

Update and Delete

import { Component, input } from "@angular/core";
import { injectWrite } from "../api/client";

@Component({
  selector: "app-user-actions",
  template: `
    <div>
      <button (click)="handleUpdate()">Update</button>
      <button (click)="handleDelete()">Delete</button>
    </div>
  `,
})
export class UserActionsComponent {
  userId = input.required<number>();

  updateUser = injectWrite((api) => api("users/:id").PUT);

  deleteUser = injectWrite((api) => api("users/:id").DELETE);

  async handleUpdate() {
    await this.updateUser.trigger({
      params: { id: this.userId() },
      body: { name: "Updated Name" },
    });
  }

  async handleDelete() {
    await this.deleteUser.trigger({ params: { id: this.userId() } });
  }
}

Dynamic Path Parameters

Spoosh supports dynamic path segments with path-based syntax:

// injectRead: With params object
user = injectRead((api) => api("users/:id").GET({ params: { id: 123 } }));

// injectRead: With reactive values
userId = input.required<number>();
user = injectRead((api) =>
  api("users/:id").GET({ params: { id: this.userId() } })
);

// injectWrite: Pass params when triggering
userId = input.required<number>();
updateUser = injectWrite((api) => api("users/:id").PUT);
// Usage: await this.updateUser.trigger({ params: { id: this.userId() }, body: { name: "New Name" } });

// Multiple params
deletePost = injectWrite((api) => api("users/:userId/posts/:postId").DELETE);
// Usage: await this.deletePost.trigger({ params: { userId: 1, postId: 42 } });

Recommended: Use the path-based syntax api("users/:id").GET({ params: { id } }) for clear and type-safe code.

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