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 in your schema keys:
type ApiSchema = {
users: {
GET: { data: User[] };
};
"users/:id": {
GET: { data: User };
PUT: { data: User; body: Partial<User> };
DELETE: void;
};
};
// Usage
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/123Nested 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/42With Angular Injection Functions
Dynamic params work seamlessly with injectRead and injectWrite:
import { Component, input, computed } from "@angular/core";
import { injectRead, injectWrite } from "./api/client";
@Component({
selector: "app-user-posts-list",
template: `
<div>
<button (click)="handleCreatePost()">Create Post</button>
@for (post of posts(); track post.id) {
<div>
<h3>{{ post.title }}</h3>
<button (click)="handleDeletePost(post.id)">Delete</button>
</div>
}
</div>
`,
})
export class UserPostsListComponent {
userId = input.required<number>();
private userPosts = injectRead((api) =>
api("users/:userId/posts").GET({ params: { userId: this.userId() } })
);
posts = computed(() => this.userPosts.data());
private createPost = injectWrite((api) => api("users/:userId/posts").POST());
private deletePost = injectWrite((api) =>
api("users/:userId/posts/:postId").DELETE()
);
async handleCreatePost() {
await this.createPost.trigger({
params: { userId: this.userId() },
body: { title: "New Post" },
});
}
async handleDeletePost(postId: number) {
await this.deletePost.trigger({
params: { userId: this.userId(), postId },
});
}
}HTTP Methods
Spoosh supports all common HTTP methods:
| Method | Description |
|---|---|
GET | GET request |
POST | POST request |
PUT | PUT request |
PATCH | PATCH request |
DELETE | DELETE request |
Schema Organization
For larger APIs, organize your schema into separate files:
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;
};
};export type PostsSchema = {
posts: {
GET: { data: Post[] };
POST: { data: Post; body: CreatePostBody };
};
};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
| Type | Description | Example |
|---|---|---|
{ data } | Endpoint with data only | GET: { data: User[] } |
{ data; body } | Endpoint with JSON body | POST: { data: User; body: CreateUserBody } |
{ data; query } | Endpoint with query params | GET: { data: User[]; query: { page: number } } |
{ data; error } | Endpoint with typed error | GET: { data: User; error: ApiError } |
void | No response body | DELETE: void |
"path/:param" | Dynamic path segment | "users/:id": { GET: { data: User } } |