---
kind: guide
zz: "0.4.0"
updated: 2026-10-07
state: draft
rules: https://zriz.io/llms.txt
---
# Test an API call and the database row

Write one zriz pipeline with two calls: a `POST` to your API, then a `select` of the new row. A check compares the row with the value that the test sent.

The example creates an order and then reads it from Postgres. The page [Pipelines](https://zriz.io/docs/pipelines.md) is the reference for each key.

> Note: The `POST` writes a row. Run the test against a test system: [Can a zriz test change my data?](https://zriz.io/docs/security.md#change-data)

## Before you start {#before-you-start}

- `zz` is installed: `zz version`
- The current folder has a project, and the sample passes: `zz run hello`

## 1. Add the API call {#add-the-api-call}

### Do

Open `.zriz/resources/target.json`. `zz init` wrote it. Add the key `headers` and the action `create-order`, and keep the other keys as they are.

- `<health path>` is the path that the file has now.
- `<path>` is the path of your API that creates an order. Example: `/api/orders`.

`.zriz/resources/target.json`:

```json
{
  "type": "http",
  "description": "Your app under test",
  "base-url": "${env.TARGET_URL}",
  "headers": { "Content-Type": "application/json" },
  "actions": {
    "check": { "method": "GET", "path": "<health path>" },
    "create-order": { "method": "POST", "path": "<path>" }
  }
}
```

```sh
zz validate hello
```

### Why

A [step](https://zriz.io/docs/words.md#step) can call only an action that a [resource](https://zriz.io/docs/words.md#resource) file names.

### You should see

```text
{"ok":true,"command":"validate",
```

### If not

- `"ok":false`: The file is not correct. `error.file` and `error.message` name the fault. Correct it, then do the command again.

## 2. Add the database {#add-the-database}

### Do

Use a database account that can only read: [Create a read-only Postgres user](https://zriz.io/docs/postgres-read-only-user.md).

Add the value `ORDERS_DB` and the key `sensitive` to the file of the [environment](https://zriz.io/docs/words.md#environment). Then make the resource file.

- `<base url>` is the value that the file has now.
- `<user>`, `<password>`, `<host>`, and `<database>` are the parts of the connection string of that account.
- `<table>` is the table that has the orders.
- `<column>` is a column that holds a text value that your API stores. Example: `note`.

`.zriz/environments/local.json`:

```json
{
  "values": {
    "TARGET_URL": "<base url>",
    "ORDERS_DB": "postgres://<user>:<password>@<host>:5432/<database>"
  },
  "sensitive": ["ORDERS_DB"]
}
```

`.zriz/resources/ordersdb.json`:

```json
{
  "type": "sql",
  "connection": "${env.ORDERS_DB}",
  "read-only": true,
  "actions": {
    "order-by-mark": { "query": "SELECT <column> FROM <table> WHERE <column> = ${ctx.mark}" }
  }
}
```

```sh
zz validate hello
```

### Why

A name in `sensitive` keeps its value on your machine. With `read-only`, the query cannot write.

### You should see

```text
{"ok":true,"command":"validate",
```

### If not

- `is not in "sensitive"`: The reply has this warning. Add `ORDERS_DB` to `sensitive` in `local.json`.
- `"ok":false`: `error.file` and `error.message` name the fault. Correct it, then do the command again.

## 3. Write the pipeline {#write-the-pipeline}

### Do

Make the [pipeline](https://zriz.io/docs/words.md#pipeline) file. It has three steps:

1. `set` makes a unique value, `mark`.
2. The call sends `mark` to your API.
3. The query finds the row that has `mark`.

- `<field>` is the field of the request body that your API stores in `<column>`. Add the other fields that your API needs.
- `<column>` is the column from step 2.
- Change `201` if your API answers a different status.

`.zriz/pipelines/create-order.json`:

```json
{
  "description": "Create an order, then find its row in the database",
  "steps": [
    { "set": { "mark": "t-${gen.uuid}" } },
    {
      "call": "target/create-order",
      "body": { "<field>": "${ctx.mark}" },
      "expect": [["status", "==", 201]]
    },
    {
      "call": "ordersdb/order-by-mark",
      "expect": [["row-count", "==", 1], ["rows[0].<column>", "==", "${ctx.mark}"]]
    }
  ]
}
```

```sh
zz validate create-order
```

### Why

A value of `set` is in `ctx` for each later step. Thus the query and its [check](https://zriz.io/docs/words.md#check) read the same value that the call sent.

### You should see

```text
{"ok":true,"command":"validate",
```

### If not

- `missing-action`: A `call` names an action that its resource file does not have. `error.valid` lists the actions. Correct the name.
- `"ok":false`: `error.file` and `error.message` name the fault. Correct it, then do the command again.

## 4. Run the test {#run-the-test}

### Do

Start your service.

```sh
zz run create-order
```

### Why

The [run](https://zriz.io/docs/words.md#run) passes only when the API answers and the row is in the database.

### You should see

```text
"status":"pass"
```

### If not

- `"status":"fail"`: Do the command in `hints` of the reply. It shows the failed step: the status of the API, or the rows of the query.
- `not logged in`: Do `zz login`. The [quick start](https://zriz.io/docs.md#log-in) gives the step.
- `too-many-runs`: The org is at the limit of the free tier. Read [What happens at the limit?](https://zriz.io/docs/limits.md#at-the-limit)

## Next {#next}

- To use a value of the reply in a later step, add `save` to the call. [Pipelines](https://zriz.io/docs/pipelines.md) gives the key.
- For MySQL, the connection string has a different form: [Can zriz test a MySQL database?](https://zriz.io/docs/supported.md#mysql)
- If your machine cannot reach the database: [Test a database in a private network](https://zriz.io/docs/test-private-database.md).
