---
name: tinystruct-patterns
description: Use this skill to build or fix tinystruct app modules and small services. Covers routes, Context, Builder JSON, settings, tests, and actions that work in both CLI and HTTP modes.
origin: ECC
---

# tinystruct Development Patterns

## Credit

This skill comes from ECC. Credit ECC in any copy or changed version.

## Use This Skill When

Use this skill when you:

- Create a module that extends `AbstractApplication`.
- Add a CLI or HTTP action with `@Action`.
- Read request data from `Context`.
- Return JSON data with `Builder`.
- Change `application.properties`.
- Create or rebuild `bin/dispatcher`.
- Fix route clashes or CLI argument errors.

## Main Rules

1. Extend `AbstractApplication`.
2. Put `@Action` only on `public` methods.
3. Use one action pattern for both CLI and HTTP when they do the same job.
4. Set the HTTP mode when an action must use a set method, such as POST.
5. Use `Builder` for JSON data.
6. Use `Context` only for data tied to the current request.
7. Turn off views for an API-only app.
8. Use `bin/dispatcher` as the main CLI entry point.
9. Let tinystruct find actions. Do not add them to `ActionRegistry` by hand.
10. Test both valid and bad input.

## Build Steps

### 1. Create the app

```java
public class MyService extends AbstractApplication {
    @Override
    public void init() {
        setTemplateRequired(false);
    }

    @Override
    public String version() {
        return "1.0.0";
    }
}
```

Call `setTemplateRequired(false)` when the app returns data and has no `.view` files.

### 2. Add a simple action

```java
@Action("greet")
public String greet() {
    return "Hello from tinystruct!";
}
```

Run it from the CLI:

```bash
bin/dispatcher greet
```

The same action can be used as an HTTP route when the app runs as a web service.

### 3. Add route values

Each regex capture group must match one method argument. Keep the same order.

```java
@Action("api/user/(\\d+)")
public String getUser(int userId) {
    return "User ID: " + userId;
}
```

Examples:

```text
HTTP: /api/user/123
CLI:  bin/dispatcher api/user/123
```

Use a narrow pattern. For an ID, prefer `(\\d+)` over `(.*)`.

### 4. Set the HTTP method

Use an HTTP mode when a route must accept only one method.

```java
@Action(value = "login", mode = Mode.HTTP_POST)
public boolean login() {
    return true;
}
```

Do not put secret data in a URL. Read passwords and tokens from the request body or request context.

### 5. Return JSON data

Use tinystruct's `Builder`. Do not add Gson or Jackson just for basic JSON.

```java
@Action("api/data")
public Builder getData() throws ApplicationException {
    Builder result = new Builder();
    result.put("status", "success");

    Builder user = new Builder();
    user.put("id", 1);
    user.put("name", "James");

    result.put("data", user);
    return result;
}
```

Keep one clear shape for both success and error data. Do not return a `Builder` on success and a plain string on failure.

### 6. Use request context with care

Use `Context` for request values, session values, and other request state.

- Check that a value exists before using it.
- Check its type before casting it.
- Do not store request state in static fields.
- Do not reuse one request's data in another request.
- Handle missing or bad values with a clear error result.

See [System and Usage](references/system-usage.md) for the exact `Context` calls.

### 7. Set app options

Put settings in:

```text
src/main/resources/application.properties
```

Use the exact property names listed in [Architecture and Settings](references/architecture.md).

Do not commit passwords, keys, or tokens. Read them from the safe setting source used by the project.

### 8. Create the dispatcher

Use `ApplicationManager.init()` when the project needs a new or rebuilt `bin/dispatcher`.

Before replacing an existing script:

- Check whether it has local changes.
- Keep its run rights.
- Run one known action after it is built.

Do not add a separate `main(String[] args)` to each module.

## Full Usage Example

This service supports one route in both CLI and HTTP modes.

```java
public class UserService extends AbstractApplication {
    @Override
    public void init() {
        setTemplateRequired(false);
    }

    @Override
    public String version() {
        return "1.0.0";
    }

    @Action("api/user/(\\d+)")
    public Builder getUser(int userId) throws ApplicationException {
        Builder result = new Builder();
        result.put("status", "success");

        Builder user = new Builder();
        user.put("id", userId);
        user.put("name", "James");

        result.put("data", user);
        return result;
    }
}
```

Run it:

```bash
bin/dispatcher api/user/123
```

Test these cases:

```text
api/user/123     should match
api/user/0       should match if zero is allowed
api/user/abc     should not match
api/user/        should not match
```

Also call the HTTP route and check that it returns the same data shape.

## Edge Cases

### Route clashes

Two actions may match the same path.

- Search all `@Action` values.
- Make each pattern as narrow as possible.
- Check fixed routes before adding broad regex routes.
- Test the path in both CLI and HTTP modes.

### Route value errors

A route may match text that the method cannot read.

- Match digits for an `int`.
- Keep capture groups and method arguments in the same order.
- Do not add a capture group unless the method accepts it.
- Test very large numbers and missing values.

### Wrong HTTP method

A route may exist but reject the request method.

- Check the action's `mode`.
- Use `Mode.HTTP_POST` for POST-only work.
- Do not let a state-changing action run through GET unless the app calls for it.

### Missing views

If an API-only app throws `FileNotFoundException` for a `.view` file, call:

```java
setTemplateRequired(false);
```

Do this in `init()`.

### Bad or missing settings

- Give clear errors for required settings.
- Do not hide a missing setting with an unsafe default.
- Keep local and test settings apart when the project supports it.
- Do not print secret values in logs or errors.

### JSON values

- Pick one rule for missing values, such as `null` or no field.
- Keep number, text, and boolean types stable.
- Use nested `Builder` values for nested objects.
- Check [Data Handling](references/data-handling.md) before adding lists or custom types.

## Tests

Use JUnit 5.

Test that:

- The app starts.
- Each action is in `ActionRegistry`.
- Valid paths reach the right action.
- Bad paths do not match.
- Capture groups map to the right argument types.
- HTTP method limits work.
- CLI and HTTP calls give the same result shape.
- API-only apps do not look for view files.
- Missing context and settings fail in a clear way.

Do not rely only on direct method tests. A direct call will not catch a bad `@Action` pattern.

## Common Problems

| Problem | Fix |
|---|---|
| Gson or Jackson was added for basic JSON | Use `org.tinystruct.data.component.Builder`. |
| A `.view` file is missing | Call `setTemplateRequired(false)` in `init()`. |
| An action is not found | Make the method `public` and check its `@Action` value. |
| A route value cannot become an `int` | Use a digit-only capture group and test its range. |
| Two actions match one path | Make the patterns more exact. |
| CLI and HTTP give different results | Put shared work in one method and keep input rules the same. |
| Each module has its own `main` method | Use `bin/dispatcher`. |
| Code adds actions to `ActionRegistry` by hand | Use `@Action` and automatic discovery. |
| Request data is stored in a static field | Keep it in `Context` or a local value. |

## Reference Files

Read only the file needed for the task:

- [Architecture and Settings](references/architecture.md): app base classes, packages, and properties.
- [Routing and @Action](references/routing.md): route patterns, modes, and arguments.
- [Data Handling](references/data-handling.md): `Builder`, nested data, and JSON values.
- [System and Usage](references/system-usage.md): `Context`, sessions, events, and CLI use.
- [Testing](references/testing.md): JUnit 5 and `ActionRegistry` tests.