documentation/getting-started/first-rpc-action.md

<!--
SPDX-FileCopyrightText: 2025 Torkild G. Kjevik
SPDX-FileCopyrightText: 2025 ash_typescript contributors <https://github.com/ash-project/ash_typescript/graphs/contributors>

SPDX-License-Identifier: MIT
-->

# 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