# Your First RPC Action 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. ## Prerequisites Complete the [Installation](installation.md) 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 ```typescript 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 ```typescript 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 ```typescript 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: ```typescript 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`: ```typescript 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: ```typescript 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: ```typescript 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](../guides/crud-operations.md) - Complete guide to all CRUD patterns - [Field Selection](../guides/field-selection.md) - Advanced field selection techniques - [Querying Data](../guides/querying-data.md) - Filtering, sorting, and pagination - [Error Handling](../guides/error-handling.md) - Comprehensive error handling strategies - [Frontend Frameworks](frontend-frameworks.md) - React, Vue, and other integrations