blast

blast docs

blast is a config-driven API load tester and mock server written in Rust. Define your endpoints in blast.config.json, then hit every one with a single command — no code, no scripting.

It supports fake data generation via {{fake.*}} placeholders, request chaining using JSON extraction and dot-path rules, fixed-RPS load tests, and stress ramp tests that auto-detect where your API breaks.

Installation

Linux & macOS

curl -fsSL https://raw.githubusercontent.com/Walon-Foundation/blast/main/install.sh | sh

Detects your OS and architecture. Downloads the pre-built binary to ~/.local/bin. See the install guide for custom install paths.

Windows (PowerShell)

irm https://raw.githubusercontent.com/Walon-Foundation/blast/main/install.ps1 | iex

Installs the x86_64-pc-windows-msvc binary and adds it to your user PATH.

Build from source

cargo install --git https://github.com/Walon-Foundation/blast

Requires Rust 1.75 or later. Cross-compile targets are listed in the repository.

Quick start

# 1. Create a starter config in the current directory
blast init

# 2. Verify the config is valid and show all endpoints
blast validate

# 3. Hit every endpoint once to confirm they respond
blast check

# 4. Seed test data (runs endpoints tagged "seed")
blast seed --count 50 --concurrency 10

# 5. Fixed-rate load test (endpoints tagged "run")
blast run --rps 50 --duration 60

# 6. Stress ramp — finds the breaking point
blast stress --min-rps 10 --max-rps 200 --step 20 --step-duration 30

Commands

blast init [path]

Creates blast.config.json in the given directory (default: current directory). The generated file includes example endpoints with fake data placeholders. Edit it to describe your API, then run blast check.

blast init
blast init ./my-api

blast validate

Loads and validates the config. Prints the base URL, total endpoint count, and a table of all endpoints with their method, path, and tags. Exits non-zero on any validation error — useful as a CI gate to catch broken configs early.

blast check

Hits every endpoint once in order. Merges global headers with per-endpoint headers. Extracts values from successful responses so later endpoints can use them. Prints a coloured pass/fail table with per-request timing. Exits non-zero on any failure.

blast seed

Runs all endpoints tagged "seed" N times with configurable concurrency. Each iteration is fully independent with its own extraction context. Use this to pre-populate a test database before a load test.

FlagDefaultDescription
--count10Total number of iterations
-j, --concurrency1Maximum parallel requests
blast seed --count 1000 --concurrency 20

blast run

Fixed-RPS load test. Uses a tokio interval ticker to maintain the target request rate. Round-robins over endpoints tagged "run". Prints live per-second progress and a final summary with p50, p95, p99, and p999 latency percentiles.

FlagDefaultDescription
--rps10Target requests per second
-d, --duration30Test duration in seconds
--ramp-up0Seconds to ramp from 0 to target RPS before measuring (0 = disabled)
--outputterminalOutput format: terminal, json, or html
--assertAssertion like "p99<200ms" or "error-rate<1%" — exits non-zero on failure
blast run --rps 100 --duration 120
blast run --rps 50 --ramp-up 30 --duration 60
blast run --output json
blast run --output html
blast run --rps 200 --assert "p99<300ms" --assert "error-rate<1%"

blast stress

RPS ramp test. Steps from --min-rps to --max-rps in increments of --step. Stops early when p99 exceeds 500ms or the error rate exceeds 1%. Prints a per-step coloured result table and a final recommendation showing the last stable RPS.

FlagDefaultDescription
--min-rps10Starting RPS
--max-rps100Maximum RPS to reach
--step10RPS increase per step
--step-duration15Seconds to hold each step
--outputterminalOutput format: terminal or json
--assertAssertion like "p99<500ms" — exits non-zero on failure
blast stress --min-rps 10 --max-rps 500 --step 50 --step-duration 20
blast stress --output json
blast stress --assert "p99<500ms"

blast mock

Starts a local HTTP server from your blast.config.json. Every endpoint becomes a live route. Frontend developers can point their app at http://localhost:<port> and build against realistic responses without waiting for the real backend.

Response bodies are read from the mock_response field on each endpoint. {{fake.*}} placeholders are resolved on every request, so each response gets fresh data. If no mock_response is defined, the route returns {"status": "ok"} with the declared status code.

FlagDefaultDescription
--port4000Port to listen on
--configblast.config.jsonPath to config (auto-detected if omitted)
blast mock
blast mock --port 8080
blast mock --config ./api/blast.config.json
$ blast mock --port 4000

  Loaded blast.config.json

  GET    /api/v1/users               200
  POST   /api/v1/auth/register       201
  POST   /api/v1/auth/login          200
  GET    /api/v1/users/{id}          200
  DELETE /api/v1/users/{id}          204

  5 routes mounted
  Listening on http://localhost:4000

blast trace <name>

Runs a single named endpoint and prints the complete request/response round-trip: resolved URL, method, headers sent, request body, response status, response headers, response body, and latency. Setup endpoints run first so extracted values are available.

Useful for debugging request/response mismatches without leaving the tool — no curl flags to remember.

blast trace "login"
blast trace "get user"
$ blast trace "login"

── request ─────────────────────────────────────
POST http://localhost:3000/api/v1/auth/login
  Content-Type: application/json
{ "email": "admin@example.com", "password": "Admin1234!" }

── response (200  14ms) ──────────────────────
  content-type: application/json
{ "data": { "access_token": "eyJ..." } }

blast stage

Runs a multi-stage load profile defined in the stages array of blast.config.json. Each stage specifies a target RPS and duration. Set rps: 0 for a cooldown (sleep) stage. Per-stage stats are printed after each step.

blast stage
{
  "stages": [
    { "rps": 10,  "duration": 30  },
    { "rps": 50,  "duration": 60  },
    { "rps": 100, "duration": 120 },
    { "rps": 0,   "duration": 30  }
  ]
}

Configuration

blast reads blast.config.json from the current directory (or the path passed to --config). The file has a flat structure — a base URL, global headers, an optional setup array, and an endpoints array.

{
  "base_url": "http://localhost:3000",
  "headers": {
    "Content-Type": "application/json",
    "Authorization": "Bearer {{token}}"
  },
  "setup": [
    {
      "name": "login",
      "method": "POST",
      "path": "/api/v1/auth/login",
      "body": { "email": "admin@example.com", "password": "Admin1234!" },
      "expect_status": 200,
      "extract": { "token": "data.access_token" }
    }
  ],
  "endpoints": [
    {
      "name": "register user",
      "method": "POST",
      "path": "/api/v1/auth/register",
      "body": {
        "email": "{{fake.email}}",
        "password": "{{fake.password}}",
        "name": "{{fake.name}}"
      },
      "expect_status": 201,
      "tags": ["seed"]
    },
    {
      "name": "list users",
      "method": "GET",
      "path": "/api/v1/users",
      "expect_status": 200,
      "weight": 3,
      "tags": ["run"]
    },
    {
      "name": "get user",
      "method": "GET",
      "path": "/api/v1/users/{{user_id}}",
      "expect_status": 200,
      "tags": ["run"]
    }
  ]
}

Endpoint fields

FieldTypeDescription
namestringHuman-readable label shown in output
methodstringHTTP method — GET, POST, PUT, PATCH, DELETE
pathstringURL path, appended to base_url. Supports {{placeholders}}.
headersobjectPer-endpoint headers, merged with global headers
bodyobjectRequest body (JSON). Supports {{fake.*}} placeholders.
expect_statusintegerExpected HTTP status code; counted as failure if response differs
extractobjectMap of variable name → dot-path to extract from response JSON
assertobjectBody assertions evaluated by blast check: { "data.count": ">0" }
weightintegerRelative traffic weight for load distribution (default: 1)
scenariostringGroups this endpoint into a named scenario sequence (e.g. "auth-flow")
tagsarrayCommands that pick up this endpoint: "seed", "run", "stress"
mock_responseobjectBody returned by blast mock for this route

Stages

The top-level stages array defines a multi-stage load profile for blast stage. Each entry sets a target RPS and duration. A stage with rps: 0 is a cooldown — blast sleeps for the duration and fires no requests.

{
  "base_url": "http://localhost:3000",
  "stages": [
    { "rps": 10,  "duration": 30  },
    { "rps": 50,  "duration": 60  },
    { "rps": 100, "duration": 120 },
    { "rps": 0,   "duration": 30  }
  ],
  "endpoints": [...]
}
FieldTypeDescription
rpsintegerTarget requests per second for this stage (0 = cooldown/sleep)
durationintegerHow long to hold this stage in seconds

Fake data

Use {{fake.*}} placeholders in request body examples or header values. A new value is generated per request — every iteration of blast seed and every request in blast run gets fresh data.

PlaceholderGenerates
{{fake.email}}Random email address (e.g. john.doe@example.com)
{{fake.username}}Random username
{{fake.password}}8–16 character password with mixed chars
{{fake.name}}Full name
{{fake.firstname}}First name only
{{fake.lastname}}Last name only
{{fake.word}}Single lorem word
{{fake.sentence}}Lorem sentence (3–8 words)
{{fake.paragraph}}Lorem paragraph (1–3 sentences)
{{fake.company}}Company name
{{fake.city}}City name
{{fake.country}}Country name
{{fake.uuid}}UUID v4
{{env.VAR_NAME}}Value of environment variable VAR_NAME

Unknown placeholders produce a warning and are left unchanged in the output so they are easy to spot.

Tags

The tags array on an endpoint controls which blast command picks it up. An endpoint can appear in multiple commands by listing multiple tags.

TagCommandNotes
"seed"blast seedRuns N times; each iteration independent
"run"blast runRound-robined for the test duration
"stress"blast stressRound-robined per step

If no endpoints have any tags, all commands fall back to using every endpoint. Endpoints in the setup array are always excluded from tag matching — they run once before load regardless of tags.

Setup phase

The top-level setup array in blast.config.json lists endpoints that run once in order before any load traffic. Extracted values from setup steps are shared with every subsequent request for the entire test.

If a setup step fails (wrong status code, network error), blast aborts immediately rather than firing load with a broken context. This prevents sending thousands of requests with a missing auth token.

The canonical use case is authentication: add a login endpoint to setup, extract the access token with extract, then include it in every load endpoint via the global headers using {{token}}.

Request chaining

The extract field on any endpoint stores response values in a shared context map keyed by variable name. Later endpoints reference them with {{name}} in any string field — headers, body values, or URL path parameters.

The dot-path walker descends into nested JSON objects (data.user.id) and array indices (items.0.id). Only scalar values (strings, numbers, booleans) are stored — objects and arrays emit a warning and are skipped.

{
  "name": "login",
  "method": "POST",
  "path": "/api/v1/auth/login",
  "body": { "email": "admin@example.com", "password": "Admin1234!" },
  "expect_status": 200,
  "extract": {
    "token":   "data.access_token",
    "user_id": "data.user.id"
  }
}
{
  "name": "get user",
  "method": "GET",
  "path": "/api/v1/users/{{user_id}}",
  "headers": { "Authorization": "Bearer {{token}}" },
  "expect_status": 200,
  "tags": ["run"]
}

Values extracted during setup are available to all subsequent endpoints. Values extracted during load operations are available to later endpoints in the same request round.

Scenarios

Adding scenario: "name" to endpoints groups them into ordered sequences. When scenarios are present in the config, blast run executes one full scenario sequence per iteration rather than round-robining individual endpoints. Each sequence runs with its own local extraction context, so values extracted in step 1 are available to step 2 — enabling realistic user journeys.

{ "name": "register",       "method": "POST", "path": "/auth/register",
  "scenario": "auth-flow",  "expect_status": 201,
  "extract": { "user_id": "data.id" } },
{ "name": "login",          "method": "POST", "path": "/auth/login",
  "scenario": "auth-flow",  "expect_status": 200,
  "extract": { "token": "data.access_token" } },
{ "name": "fetch profile",  "method": "GET",  "path": "/users/{{user_id}}",
  "scenario": "auth-flow",  "headers": { "Authorization": "Bearer {{token}}" },
  "expect_status": 200 }

Variable files

Pass --vars path/to/vars.json to any command to inject a flat JSON object of variables into the template context. These have the lowest precedence — they are overridden by values extracted from responses or set by environment variables.

{
  "base_user": "admin@example.com",
  "org_id":    "acme-corp",
  "tier":      "enterprise"
}
blast run --vars staging.json
blast check --vars ./env/prod.json

Only scalar values (strings, numbers, booleans) are accepted. Nested objects and arrays produce a warning and are skipped.

Threshold assertions

Pass one or more --assert flags to blast run or blast stress to enforce performance thresholds. blast evaluates assertions after the test and exits non-zero if any fail — enabling performance gates in CI pipelines.

MetricExampleDescription
p50p50<100msMedian latency
p95p95<200ms95th percentile latency
p99p99<500ms99th percentile latency
p999p999<1000ms99.9th percentile latency
error-rateerror-rate<1%Percentage of failed requests
success-ratesuccess-rate>99%Percentage of successful requests
blast run --rps 100 --duration 60 \
  --assert "p99<200ms" \
  --assert "error-rate<1%"

# exits 0 if all pass, non-zero with details if any fail

Body assertions (per-endpoint) use the assert field and are evaluated by blast check. See Endpoint fields.

History

After every blast run or blast stress, blast automatically saves the result to ~/.blast/history/. On the next run against the same config, it prints a one-line comparison showing how p50, p95, p99, and p999 changed.

$ blast run --rps 100 --duration 60

  ...

  vs last run:
    p50   42ms 38ms  (-9%) ▼
    p95   89ms 91ms  (+2%) →
    p99  142ms 198ms (+39%) ▲

No flags needed — history runs silently and never affects the exit code. Records are keyed by the canonical config file path.