---
title: "Common Blueprint Patterns · RUAL Documentation"
description: "Reusable shapes for validation, mapping arrays, caching, error handling and API auth."
canonical: https://docs.rual.nl/blueprints/common-patterns
language: en
---

# Common Blueprint Patterns

Copyable blueprint recipes for everyday tasks: form validation, API auth, the JSON response backbone, data transformation, error handling, and caching with Redis and storage events.

Each pattern below is a mini-recipe you can copy straight onto your canvas: the goal, when to use it, the blocks involved with their wiring, and notes from practice. Every block mentioned exists in the current block library. Search its type name in the blueprint editor to add it.

### 1. Form Validation

**Goal:** Reject incomplete submissions before they reach storage.

**When to use:** Any state page that accepts user input through a form and writes it to storage or passes it to a function.

**Recipe:**

| Block | Purpose | Connects To |
| --- | --- | --- |
| [`state_form`](https://docs.rual.nl/block-types/state%20ui/state_form) (form) | Renders the form and collects user input into the state. | Submit button triggers [`state_form_get`](https://docs.rual.nl/block-types/state%20ui/state_form_get) |
| [`state_form_get`](https://docs.rual.nl/block-types/state%20ui/state_form_get) (get form data) | Retrieves the submitted form data from the state. Exposes a `form-object` out-pin with the field values and a `form-error` value pin for form-level errors. | Condition blocks, one per required field |
| [`condition_not_empty_value`](https://docs.rual.nl/block-types/condition/condition_not_empty_value) (is not empty) | Returns `true` when the field value from `form-object` is filled. | A condition branch guarding the save flow |
| [`function_console_log`](https://docs.rual.nl/block-types/logging/function_console_log) (log) | Optional: writes validation failures to the blueprint console while developing. | The failing branch of the condition |

Wire every required field of the `form-object` into its own [`condition_not_empty_value`](https://docs.rual.nl/block-types/condition/condition_not_empty_value) block. Only when all conditions pass does the flow continue to the create or update document blocks; otherwise it branches to an error message in the UI.

**Notes:**

- Validate *before* touching storage. A rejected submission should never produce a document.

- Keep one condition block per field instead of one combined check, so the UI can tell the user exactly which field failed.

- Inspect the `form-error` pin first: form-level errors mean the submitted data could not be read at all.

### 2. API Authentication

**Goal:** Expose an API endpoint only to the right callers, with explicit error replies for everyone else.

**When to use:** Any custom API built with an `on_startup_register_uri_{method}` block that should not be fully public.

**Recipe:**

| Block | Purpose |
| --- | --- |
| [`on_startup_register_uri_get`](https://docs.rual.nl/block-types/http%20connection/on_startup_register_uri_get) (on api get) | Exposes a GET URI on `/api/`. Set its scopes through the lock icon: `*public` (default, everyone), `*loggedin` (authenticated users only), or a custom scope. |
| [`user_current`](https://docs.rual.nl/block-types/users/user_current) (current user) | Returns the logged-in user object, so the flow can identify who is calling. |
| [`httpconnection_current_request`](https://docs.rual.nl/block-types/http%20connection/httpconnection_current_request) (current request) | Retrieves a reference to the current HTTP connection, needed to send an explicit reply. |
| [`httpconnection_set_json`](https://docs.rual.nl/block-types/json/httpconnection_set_json) (reply in json) | Replies to the connection with a JSON body and a status `code` in-pin. Use `401` for unauthenticated and `403` for unauthorized callers. |

With the `*loggedin` scope, the cluster rejects requests without a valid `access_token` before your flow runs. Inside the flow, [`user_current`](https://docs.rual.nl/block-types/users/user_current) plus a condition on the user's scopes lets you add finer-grained checks and answer failures yourself through [`httpconnection_set_json`](https://docs.rual.nl/block-types/json/httpconnection_set_json).

**Notes:**

- Callers can pass the token five ways: `Authorization: Bearer` header, `x-authtoken` or `x-token` header, an `access_token` cookie, or the `?access_token=` query parameter. See [Adding tokens to your requests](https://docs.rual.nl/cluster/api#token-providing). Tokens are valid for 14 days and extend automatically while actively used ([Token Expiry](https://docs.rual.nl/cluster/api#token-expiry)).

- Protect the endpoint further with a [rate limit or throttle](https://docs.rual.nl/blueprints/remote-access-control) in a few clicks.

- An endpoint that still returns 404 after scoping usually was never activated. Press `Activate` and deploy for production traffic. See [API not activating](https://docs.rual.nl/troubleshooting/common-issues#api-not-activating).

### 3. The JSON API Response Backbone

**Goal:** Answer an API call with a clean, shaped JSON body.

**When to use:** Every custom endpoint handler. This chain is the most common wiring in production blueprints by a wide margin: across the production blueprints on our own cluster, [`httpconnection_set_json`](https://docs.rual.nl/block-types/json/httpconnection_set_json) is fed by [`httpconnection_current_request`](https://docs.rual.nl/block-types/http%20connection/httpconnection_current_request) more than any other pair (541 wires), and [`object_field_getter_multiple`](https://docs.rual.nl/block-types/object/object_field_getter_multiple) is the most-used non-input block of all (571 instances across 79 blueprints).

**Recipe:**

| Block | Role in the chain |
| --- | --- |
| [`on_startup_register_uri_get`](https://docs.rual.nl/block-types/http%20connection/on_startup_register_uri_get) (on api get) | Registers the endpoint; its function pin roots the handler function. |
| [`httpconnection_current_request`](https://docs.rual.nl/block-types/http%20connection/httpconnection_current_request) (current request) | The connection reference: feeds the input readers and the reply block. |
| [`httpconnection_get_body`](https://docs.rual.nl/block-types/http%20connection/httpconnection_get_body) (get body) / [`httpconnection_get_params`](https://docs.rual.nl/block-types/http%20connection/httpconnection_get_params) (get params) | Reads the request input: the body object on writes, the URL parameters on reads. |
| [`object_field_getter_multiple`](https://docs.rual.nl/block-types/object/object_field_getter_multiple) (get fields) | Outputs one pin per selected field of the request or document object, so every following block wires a named field instead of digging into an object. |
| [`object_new_fields`](https://docs.rual.nl/block-types/object/object_new_fields) (new object) | Builds the response object with only the fields the caller may see. |
| [`httpconnection_set_json`](https://docs.rual.nl/block-types/json/httpconnection_set_json) (reply in json) + [`number_default`](https://docs.rual.nl/block-types/number/number_default) (number) | Sends the object with an explicit status `code`: 200 on the happy path, 4xx on the guarded paths. |

**Notes:**

- Reply on every path, including validation failures and not-found branches. A caller left without a reply sees a timeout, not an error.

- Shape the response with [`object_new_fields`](https://docs.rual.nl/block-types/object/object_new_fields) even when the document already looks right: it keeps `_meta` internals and future private fields from leaking into the API.

- Reading input fields with [`object_field_getter_multiple`](https://docs.rual.nl/block-types/object/object_field_getter_multiple) instead of ad-hoc object access keeps the handler readable when the payload grows.

### 4. Data Transformation

**Goal:** Turn storage documents into exactly the shape your API or UI needs: no more, no less.

**When to use:** Shaping API responses, converting arrays of documents, and writing denormalized data.

**Recipe A: shape a response object:** Feed the fields you want to expose into an [`object_new_fields`](https://docs.rual.nl/block-types/object/object_new_fields) (new object) block, which builds a fresh object containing only those fields. Use [`object_update_fields`](https://docs.rual.nl/block-types/object/object_update_fields) when you want to adjust an existing object instead of building a new one. This keeps internal fields such as `_meta` internals out of your API responses.

```
{
  "guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
  "username": "joe",
  "email": "joe@example.com"
}
```

**Recipe B: transform an array:** Connect the array to an [`array_map`](https://docs.rual.nl/block-types/array/array_map) (map) block. It applies the connected function to every element and returns a new array with the results: the way to run per-element logic (formatting, lookups, parsing) over a [`function_search`](https://docs.rual.nl/block-types/storage/function_search) result. It accepts a `concurrency` number in-pin and exposes an `error` value out-pin; [`array_map_parse`](https://docs.rual.nl/block-types/array/array_map_parse) (map and parse) is the variant for parsing each element along the way. For simply picking fields off each document, the getter-plus-object shape from the response backbone above is what production blueprints use most; reach for map when each element needs its own flow.

**Recipe C: denormalized writes:** Because RUAL storage has no relationships, include all data you need inside each document. Build the fields with [`mutations_set_bp_field_multiple`](https://docs.rual.nl/block-types/mutations/mutations_set_bp_field_multiple) and write them with [`function_create_document_from_mutations`](https://docs.rual.nl/block-types/storage/function_create_document_from_mutations) (create document) or [`function_update_document_mutations`](https://docs.rual.nl/block-types/storage/function_update_document_mutations) (update document). When a source document changes, keep the embedded copies in sync with a [`storage_event`](https://docs.rual.nl/block-types/storage/storage_event) flow. See [Data Modelling Without Relations](https://docs.rual.nl/blueprints/storage#data-modelling).

### 5. Error Handling

**Goal:** Make failures visible, answer them properly, and retry work that deserves a second attempt.

**When to use:** Every flow that touches storage, HTTP requests, or any other operation that can fail. RUAL has no try/catch. Blocks that can fail expose a `success` condition pin and an `error` value pin, and the flow keeps running unless you branch on them.

**Recipe: log and reply (error_demo style):**

| Step | Wiring |
| --- | --- |
| 1. Branch on the result | Connect the block's `success` pin to a condition block: `true` continues the happy path, `false` enters the error path. |
| 2. Log the failure | Connect the `error` pin to the `message` in-pin of [`function_console_log`](https://docs.rual.nl/block-types/logging/function_console_log) (log), so the failure appears in the blueprint console. |
| 3. Reply with an error | Reply through [`httpconnection_set_json`](https://docs.rual.nl/block-types/json/httpconnection_set_json) with a `4xx`/`5xx` code for APIs, or show an error message in the UI for pages. |

A typical error payload looks like this:

```
{
  "success": false,
  "error": "Document not found for the given GUID"
}
```

**Notes:**

- **Retry via the queue:** for background work that may fail transiently, (re)schedule it with [`function_custom_execute_from_queue`](https://docs.rual.nl/block-types/function%20execution/function_custom_execute_from_queue) (execute in queue). It runs the function at a given time without waiting, and accepts an optional `unique_id` and `debounce` to avoid duplicate runs. See [Queue](https://docs.rual.nl/blueprints/queue).

- **Audit failures yourself:** the blueprint audit log records *modifications*, not runtime errors. For a failure trail, write errors to a dedicated storage with [`function_create_document_from_mutations`](https://docs.rual.nl/block-types/storage/function_create_document_from_mutations) or send them to an external webhook. The patterns from [Error Handling](https://docs.rual.nl/blueprints/block-execution#error-handling).

- An unconnected `error` pin means failures pass silently. Always wire it somewhere. See [Reading error messages](https://docs.rual.nl/troubleshooting/debugging#error-messages).

### 6. Caching Strategies

**Goal:** Stop paying for the same query twice.

**When to use:** Read-heavy flows and high-traffic pages. A single search is cheap, but 300 simultaneous page visits run it 300 times a minute.

**Recipe A: built-in search cache:** [`function_search`](https://docs.rual.nl/block-types/storage/function_search) has a `Cache Key` value in-pin. Pass a key that identifies the query (for example the searched user GUID) and repeated identical searches are served from cache instead of hitting storage again.

**Recipe B, manual Redis check-aside:**

| Block | Role in the pattern |
| --- | --- |
| [`value_redis_cache_get_key`](https://docs.rual.nl/block-types/redis/value_redis_cache_get_key) (get cache) | Reads the cached value for a key before you query storage. |
| [`function_redis_cache_get_exists`](https://docs.rual.nl/block-types/redis/function_redis_cache_get_exists) (get cache key) | Flow-based variant: returns an `exists` condition plus the value, ideal for the cache-hit / cache-miss branch. |
| [`function_redis_cache_set_key_json`](https://docs.rual.nl/block-types/redis/function_redis_cache_set_key_json) (set json) / [`function_redis_cache_set_key`](https://docs.rual.nl/block-types/redis/function_redis_cache_set_key) (set) | On a cache miss, run the search and store the result (JSON or string) under the key. Both accept an optional `ttl` in seconds. |
| [`function_redis_cache_ttl`](https://docs.rual.nl/block-types/redis/function_redis_cache_ttl) (set cache ttl) | Adjusts the time-to-live of an existing key. |

**Recipe C: invalidate on write:** cached data must die when the underlying documents change. Add a [`storage_event`](https://docs.rual.nl/block-types/storage/storage_event) (trigger event) flow on the storage. It fires on created, updated, removed, and saved events: and remove the affected keys with [`function_redis_cache_delete_key`](https://docs.rual.nl/block-types/redis/function_redis_cache_delete_key) (delete), or overwrite them with fresh data right away.

**Recipe D: precompiled static lookups:** for values that never change, reuse one block for identical values across the blueprint instead of duplicating blocks. RUAL [precompiles](https://docs.rual.nl/blueprints/precompiled) block outputs for efficiency and reuses them on every execution.

**Notes:**

- Never cache what must be real-time. If a screen must show the absolute latest data, prefer the [`disable cache`](https://docs.rual.nl/block-types/query/query_disabled_request_cache) query option over cached results: and read the trade-off in [Common Pitfalls](https://docs.rual.nl/blueprints/common-pitfals#disable-cache-query).

- Invalidate narrowly: delete or overwrite the keys affected by the write instead of flushing broad key ranges.

- Background on cache strategy lives in [Caching with Redis](https://docs.rual.nl/blueprints/storage#caching) and [Common Pitfalls](https://docs.rual.nl/blueprints/common-pitfals).
