This guide walks you through making your first type-safe API call with AshTypescript. By the end, you'll understand the core concepts that make AshTypescript powerful.
Complete the Installation guide first.
After running mix ash.codegen, you'll have a TypeScript file (e.g., assets/js/ash_rpc.ts) containing:
- Type definitions for your Ash resources
- RPC functions for each exposed action
- Field selection types for type-safe queries
- Helper utilities like
buildCSRFHeaders()
import { listTodos } from './ash_rpc';
async function fetchTodos() {
const result = await listTodos({
fields: ["id", "title", "completed"]
});
if (result.success) {
console.log("Todos:", result.data);
} else {
console.error("Error:", result.errors);
}
}Key concept: Field Selection
The fields parameter specifies exactly which fields you want returned. This provides:
- Reduced payload size - only requested data is sent
- Better performance - Ash only loads what you need
- Full type safety - TypeScript knows the exact shape of your response
import { createTodo } from './ash_rpc';
async function addTodo(title: string) {
const result = await createTodo({
fields: ["id", "title", "createdAt"],
input: {
title: title,
priority: "medium"
}
});
if (result.success) {
console.log("Created:", result.data);
return result.data;
} else {
console.error("Failed:", result.errors);
return null;
}
}import { getTodo } from './ash_rpc';
async function fetchTodo(id: string) {
const result = await getTodo({
fields: ["id", "title", "completed", "priority"],
input: { id }
});
if (result.success) {
console.log("Todo:", result.data);
}
}One of AshTypescript's powerful features is nested field selection for relationships:
const result = await getTodo({
fields: [
"id",
"title",
{
user: ["name", "email"],
tags: ["name", "color"]
}
],
input: { id: "123" }
});
if (result.success) {
console.log("Todo:", result.data.title);
console.log("Created by:", result.data.user.name);
console.log("Tags:", result.data.tags.map(t => t.name).join(", "));
}TypeScript automatically infers the correct types for nested relationships.
All RPC functions return a discriminated union with success: true or success: false:
const result = await createTodo({
fields: ["id", "title"],
input: { title: "New Todo" }
});
if (result.success) {
// TypeScript knows result.data exists here
const todo = result.data;
console.log("Created:", todo.id);
} else {
// TypeScript knows result.errors exists here
result.errors.forEach(error => {
console.error(`${error.message}`);
// Field-specific errors include the field name
if (error.fields.length > 0) {
console.error(` Fields: ${error.fields.join(', ')}`);
}
});
}For requests that require authentication, pass headers:
import { listTodos, buildCSRFHeaders } from './ash_rpc';
// With CSRF protection (for browser-based apps)
const result = await listTodos({
fields: ["id", "title"],
headers: buildCSRFHeaders()
});
// With Bearer token authentication
const result = await listTodos({
fields: ["id", "title"],
headers: {
"Authorization": "Bearer your-token-here"
}
});
// Combining both
const result = await listTodos({
fields: ["id", "title"],
headers: {
...buildCSRFHeaders(),
"Authorization": "Bearer your-token-here"
}
});Here's a complete example showing all CRUD operations:
import {
listTodos,
getTodo,
createTodo,
updateTodo,
destroyTodo,
buildCSRFHeaders
} from './ash_rpc';
const headers = buildCSRFHeaders();
// CREATE
const createResult = await createTodo({
fields: ["id", "title"],
input: { title: "Learn AshTypescript", priority: "high" },
headers
});
if (!createResult.success) {
console.error("Create failed:", createResult.errors);
return;
}
const todoId = createResult.data.id;
// READ (single)
const getResult = await getTodo({
fields: ["id", "title", "priority", { user: ["name"] }],
input: { id: todoId },
headers
});
// READ (list)
const listResult = await listTodos({
fields: ["id", "title", "completed"],
headers
});
// UPDATE
const updateResult = await updateTodo({
fields: ["id", "title", "updatedAt"],
identity: todoId,
input: { title: "Mastered AshTypescript" },
headers
});
// DELETE
const deleteResult = await destroyTodo({
identity: todoId,
headers
});Now that you understand the basics, explore:
- CRUD Operations - Complete guide to all CRUD patterns
- Field Selection - Advanced field selection techniques
- Querying Data - Filtering, sorting, and pagination
- Error Handling - Comprehensive error handling strategies
- Frontend Frameworks - React, Vue, and other integrations