Skip to main content
Fixtures and templating are the foundation of dynamic responses in Apicentric. Fixtures provide reusable test data, while Handlebars templating allows you to generate realistic, context-aware responses.

What are fixtures?

Fixtures are predefined data objects stored in your service definition. They act as an in-memory database for your mock API, providing consistent test data across all endpoints.

Defining fixtures

Fixtures are defined in the fixtures section of your service definition:
fixtures:
  users:
    - id: 1
      username: "alice"
      email: "[email protected]"
      role: "admin"
    - id: 2
      username: "bob"
      email: "[email protected]"
      role: "user"
  
  products:
    - id: 101
      name: "Laptop Pro"
      price: 1299.99
      category: "electronics"
      stock: 15
    - id: 102
      name: "Coffee Mug"
      price: 12.50
      category: "home"
      stock: 50
Fixtures can contain any valid JSON data structure: objects, arrays, strings, numbers, booleans, and nested structures.

Fixture structure

You can organize fixtures in several ways:
fixtures:
  tasks:
    - id: 1
      title: "Setup environment"
      completed: false
    - id: 2
      title: "Write tests"
      completed: true

Handlebars templating

Apicentric uses Handlebars as its template engine. Templates allow you to create dynamic responses that reference fixtures, request data, and runtime state.

Basic syntax

Handlebars expressions are wrapped in double curly braces:
responses:
  200:
    content_type: application/json
    body: |
      {
        "id": {{params.id}},
        "username": "{{request.body.username}}",
        "email": "{{request.body.email}}",
        "created_at": "{{now}}"
      }

Template context

Your templates have access to several context objects:

fixtures

Access fixture data defined in your service:
{{fixtures.users}}
{{fixtures.products.0.name}}

params

Path parameters from the URL:
{{params.id}}
{{params.userId}}
{{params.orderId}}
For the path /users/{id}, a request to /users/123 provides {{params.id}} = "123".

request.query

Query string parameters:
{{request.query.category}}
{{request.query.page}}
{{request.query.limit}}
For the URL /products?category=electronics&page=2, you get:
  • {{request.query.category}} = "electronics"
  • {{request.query.page}} = "2"

request.headers

HTTP request headers:
{{request.headers.authorization}}
{{request.headers.x-request-id}}
{{request.headers.user-agent}}

request.body

Parsed request body (for POST/PUT/PATCH):
{{request.body.username}}
{{request.body.email}}
{{request.body.items.0.quantity}}

request.method and request.path

HTTP method and request path:
{{request.method}}  // "GET", "POST", etc.
{{request.path}}    // "/api/v1/users"

runtime

Runtime state data (mutable across requests):
{{runtime.counter}}
{{runtime.lastUpdated}}

bucket

Persistent storage bucket:
{{bucket.lastCreated}}
{{bucket.sessionData}}

env

Environment variables:
{{env.API_KEY}}
{{env.DATABASE_URL}}

Built-in helpers

Apicentric provides many Handlebars helpers for common operations.

Iteration helpers

each

Iterate over arrays:
body: |
  {
    "users": [
      {{#each fixtures.users}}
      {
        "id": {{id}},
        "username": "{{username}}",
        "email": "{{email}}"
      }{{#unless @last}},{{/unless}}
      {{/each}}
    ]
  }
Use {{#unless @last}},{{/unless}} to add commas between items but not after the last one.
Special variables in each loops:
  • @index - Current iteration index (0-based)
  • @first - True for the first item
  • @last - True for the last item
  • @key - Key name when iterating over objects

Conditional helpers

if/else

Conditional rendering:
body: |
  {
    "status": "{{#if request.query.active}}active{{else}}inactive{{/if}}",
    "message": "{{#if params.id}}Found{{else}}Not found{{/if}}"
  }

Comparison helpers

Compare values:
# Equal
{{#if (eq params.id "1")}}Match{{/if}}

# Not equal
{{#if (ne params.status "pending")}}Complete{{/if}}

# Greater than
{{#if (gt params.id "100")}}Large ID{{/if}}

# Less than
{{#if (lt params.price "50")}}Cheap{{/if}}

# Greater than or equal
{{#if (gte params.age "18")}}Adult{{/if}}

# Less than or equal
{{#if (lte params.score "100")}}Valid{{/if}}

Logical helpers

Combine conditions:
# AND
{{#if (and request.body.username request.body.email)}}
  Valid input
{{/if}}

# OR
{{#if (or (eq params.role "admin") (eq params.role "moderator"))}}
  Has permissions
{{/if}}

# NOT
{{#if (not request.body.optional)}}
  Field is empty
{{/if}}

Data manipulation helpers

json

Serialize values to JSON:
body: |
  {
    "items": {{json request.body.items}},
    "metadata": {{json fixtures.config}}
  }

find

Find an item in an array:
{{find fixtures.users "id" params.id}}

filter

Filter array items:
{{filter fixtures.products "category" request.query.category}}

default

Provide a default value:
"page": {{default request.query.page "1"}}

Faker helpers

Generate realistic random data:
body: |
  {
    "id": {{faker "datatype.number" min=1000 max=9999}},
    "name": "{{faker "name.fullName"}}",
    "email": "{{faker "internet.email"}}",
    "phone": "{{faker "phone.number"}}",
    "address": {
      "street": "{{faker "address.streetAddress"}}",
      "city": "{{faker "address.city"}}",
      "zipCode": "{{faker "address.zipCode"}}"
    },
    "price": {{faker "commerce.price" min=10 max=1000}},
    "product": "{{faker "commerce.productName"}}",
    "company": "{{faker "company.name"}}",
    "description": "{{faker "lorem.paragraph"}}",
    "uuid": "{{faker "datatype.uuid"}}"
  }
Person data:
  • name.fullName - Full name
  • name.firstName - First name
  • name.lastName - Last name
Internet:
  • internet.email - Email address
  • internet.url - URL
  • internet.userName - Username
  • internet.password - Password
Commerce:
  • commerce.productName - Product name
  • commerce.price - Price (with min/max)
  • commerce.department - Department
  • commerce.color - Color name
Company:
  • company.name - Company name
  • company.catchPhrase - Company slogan
Address:
  • address.streetAddress - Street address
  • address.city - City name
  • address.state - State name
  • address.zipCode - ZIP code
  • address.country - Country name
Lorem:
  • lorem.word - Random word
  • lorem.sentence - Random sentence
  • lorem.paragraph - Random paragraph
Data types:
  • datatype.number - Random number (with min/max)
  • datatype.uuid - UUID v4
  • datatype.boolean - Random boolean
Phone:
  • phone.number - Phone number

Date/time helpers

now

Current ISO timestamp:
"created_at": "{{now}}"
Output: "2024-01-15T10:30:00Z"

Math helpers

Perform calculations:
# Addition
"total": {{add 10 5}}  // 15

# Subtraction
"remaining": {{subtract 100 25}}  // 75

# Multiplication
"price": {{multiply 10 1.5}}  // 15

# Division
"average": {{divide 100 4}}  // 25

Text helpers

Manipulate strings:
# Uppercase
"code": "{{uppercase request.body.code}}"

# Lowercase
"email": "{{lowercase request.body.email}}"

# Capitalize
"name": "{{capitalize request.body.name}}"

Random helper

Pick a random value from a list:
"status": "{{#random}}pending,processing,shipped,delivered{{/random}}"

Real-world examples

User list with pagination

fixtures:
  users:
    - id: 1
      username: "alice"
      email: "[email protected]"
    - id: 2
      username: "bob"
      email: "[email protected]"
    - id: 3
      username: "charlie"
      email: "[email protected]"

endpoints:
  - method: GET
    path: /users
    responses:
      200:
        content_type: application/json
        body: |
          {
            "users": [
              {{#each fixtures.users}}
              {
                "id": {{id}},
                "username": "{{username}}",
                "email": "{{email}}"
              }{{#unless @last}},{{/unless}}
              {{/each}}
            ],
            "page": {{default request.query.page "1"}},
            "total": {{fixtures.users.length}}
          }

Product search with filtering

fixtures:
  products:
    - id: 101
      name: "Laptop Pro"
      category: "electronics"
      price: 1299.99
    - id: 102
      name: "Coffee Mug"
      category: "home"
      price: 12.50

endpoints:
  - method: GET
    path: /products
    responses:
      200:
        content_type: application/json
        body: |
          {
            "products": {{json (filter fixtures.products "category" request.query.category)}},
            "filter": "{{request.query.category}}",
            "total": {{fixtures.products.length}}
          }

Order creation with dynamic data

endpoints:
  - method: POST
    path: /orders
    responses:
      201:
        content_type: application/json
        body: |
          {
            "order_id": {{faker "datatype.number" min=1000 max=9999}},
            "customer_id": {{request.body.customer_id}},
            "items": {{json request.body.items}},
            "total": {{faker "commerce.price" min=10 max=500}},
            "status": "pending",
            "created_at": "{{now}}",
            "tracking_number": "{{faker "datatype.uuid"}}"
          }

Conditional response based on user role

fixtures:
  users:
    - id: 1
      username: "alice"
      role: "admin"
    - id: 2
      username: "bob"
      role: "user"

endpoints:
  - method: GET
    path: /users/{id}
    responses:
      200:
        content_type: application/json
        body: |
          {{#with (find fixtures.users "id" params.id)}}
          {
            "id": {{id}},
            "username": "{{username}}",
            "role": "{{role}}",
            {{#if (eq role "admin")}}
            "permissions": ["read", "write", "delete"],
            {{else}}
            "permissions": ["read"],
            {{/if}}
            "is_admin": {{eq role "admin"}}
          }
          {{/with}}

Bucket for persistent state

The bucket is a special storage area for data that persists across requests and can be modified by side effects.

Defining bucket data

bucket:
  counter: 0
  lastUser: null
  sessionData: {}

Using bucket in templates

body: |
  {
    "counter": {{bucket.counter}},
    "lastUser": "{{bucket.lastUser}}"
  }

Modifying bucket with side effects

responses:
  201:
    content_type: application/json
    body: |
      {"created": true}
    side_effects:
      - action: "set"
        target: "bucket.counter"
        value: "{{add bucket.counter 1}}"
      - action: "set"
        target: "bucket.lastUser"
        value: "{{request.body.username}}"

Best practices

Template best practices:
  1. Keep templates readable: Use proper indentation and formatting
  2. Use comments: Document complex template logic with YAML comments
  3. Test edge cases: Verify templates work with missing/null values
  4. Use helpers: Leverage built-in helpers instead of complex logic
  5. Validate JSON: Ensure your templates produce valid JSON
  6. Escape strings: Use quotes around string values in JSON
Common pitfalls:
  • Missing commas in JSON: Use {{#unless @last}},{{/unless}} in loops
  • Unescaped quotes: Be careful with quotes in string values
  • Type mismatches: Numbers should not be quoted in JSON
  • Null/undefined values: Provide defaults for optional fields

Next steps

Scenarios

Learn how to simulate different API states

Service definitions

Master the complete YAML structure

Build docs developers (and LLMs) love