Your First RPC Action
View SourceThis 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.
Prerequisites
Complete the Installation guide first.
Understanding the Generated Code
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()
Making Your First Call
List Records
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
Create a Record
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;
}
}Get a Single Record
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);
}
}Including Relationships
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.
Handling Errors
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(', ')}`);
}
});
}Adding Authentication
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"
}
});Complete Example
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
});What's Next?
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