Inline arguments allow you to pass dynamic data directly to middleware calls, making pipelines more concise and expressive. Instead of using JQ steps to set intermediate variables, you can compute values inline within the middleware invocation.
Traditional Approach:
|> jq: `{ sqlParams: [.userId, "active"] }`
|> pg: `SELECT * FROM users WHERE id = $1 AND status = $2`
Inline Argument Approach:
|> pg([.userId, "active"]): `SELECT * FROM users WHERE id = $1 AND status = $2`
Inline arguments reduce boilerplate and keep the data flow more visible in the pipeline.
Use parentheses () to pass inline arguments:
|> middleware(argument)
|> middleware(argument1, argument2)
Pass array parameters using square brackets [...]:
|> pg([.userId, .status]): `SELECT * FROM users WHERE id = $1 AND status = $2`
Pass object parameters using curly braces {...}:
|> graphql({ userId: .targetId, limit: 10 }): `query($userId: ID!, $limit: Int) { ... }`
Pass string literals or JQ string expressions:
|> fetch("https://api.example.com/users/" + (.userId | tostring))
|> log("Processing user request")
Array parameters are primarily used with the pg middleware for SQL parameter binding.
GET /users/:id
|> jq: `{ id: (.params.id | tonumber) }`
|> pg([.id]): `SELECT * FROM users WHERE id = $1`
GET /users/search
|> jq: `{
status: .query.status,
role: .query.role
}`
|> pg([.status, .role]): `
SELECT * FROM users
WHERE status = $1 AND role = $2
`
POST /orders
|> pg([.body.userId, .body.productId, .body.quantity]): `
INSERT INTO orders (user_id, product_id, quantity)
VALUES ($1, $2, $3)
RETURNING *
`
Arrays can contain different JQ expressions:
|> pg([(.params.id | tonumber), .query.status, (.body.limit // 10)]): `
SELECT * FROM items
WHERE id = $1 AND status = $2
LIMIT $3
`
Build URLs or strings dynamically using JQ expressions.
GET /api/users/:id/profile
|> jq: `{ userId: (.params.id | tostring) }`
|> fetch("https://api.example.com/users/" + .userId)
GET /external/:resource/:id
|> jq: `{
baseUrl: "https://api.example.com",
resource: .params.resource,
id: .params.id,
apiKey: $ENV.API_KEY
}`
|> fetch(.baseUrl + "/" + .resource + "/" + (.id | tostring) + "?key=" + .apiKey)
GET /search
|> jq: `{
term: .query.q,
limit: (.query.limit // 10 | tostring)
}`
|> fetch("https://api.example.com/search?q=" + .term + "&limit=" + .limit)
Object parameters are used with graphql middleware for passing variables.
GET /gql/user/:id/todos
|> jq: `{ targetId: (.params.id | tonumber) }`
|> graphql({ userId: .targetId }): `
query($userId: ID!) {
user(id: $userId) {
name
email
}
posts(limit: 10) {
id
title
body
}
}
`
GET /gql/posts
|> jq: `{
authorId: (.query.author | tonumber),
pageSize: (.query.limit // 10 | tonumber),
offset: (.query.offset // 0 | tonumber)
}`
|> graphql({
author: .authorId,
limit: .pageSize,
offset: .offset
}): `
query($author: ID!, $limit: Int!, $offset: Int!) {
posts(authorId: $author, limit: $limit, offset: $offset) {
id
title
body
createdAt
}
}
`
POST /gql/create-user
|> jq: `{
userData: {
name: .body.name,
email: .body.email,
role: .body.role // "user"
}
}`
|> graphql({ input: .userData }): `
mutation($input: UserInput!) {
createUser(input: $input) {
id
name
email
createdAt
}
}
`
The pg middleware accepts array parameters for SQL binding:
|> pg([.userId, .status]): `SELECT * FROM users WHERE id = $1 AND status = $2`
Benefits:
- Automatic SQL injection protection
- Type coercion handled by sqlx
- Clear parameter mapping
The fetch middleware accepts string URLs:
|> fetch("https://api.example.com/data")
|> fetch(.customUrl)
|> fetch("https://api.example.com/users/" + (.userId | tostring))
Additional Options: You can still use the traditional approach for complex configurations:
|> jq: `{
fetchUrl: "https://api.example.com/data",
fetchMethod: "POST",
fetchBody: { key: "value" },
fetchHeaders: { "Authorization": "Bearer " + .token }
}`
|> fetch: `_`
The graphql middleware accepts object parameters for variables:
|> graphql({ userId: .id, limit: 10 }): `query($userId: ID!, $limit: Int!) { ... }`
Benefits:
- Clearer variable mapping
- Type safety through GraphQL schema
- Less boilerplate than setting
graphqlParams
The log middleware accepts string messages:
|> log("Starting user registration")
|> log("Processing order for user " + (.userId | tostring))
The cache middleware accepts string or object configuration:
|> cache: `ttl: 300, enabled: true`
|> cache({ ttl: 300, keyTemplate: "user-" + (.userId | tostring) })
The rateLimit middleware accepts configuration objects:
|> rateLimit({
keyTemplate: "user-" + (.userId | tostring),
limit: 100,
window: 3600
})
The loader middleware is used exclusively in GraphQL field resolvers to batch data loading and prevent N+1 queries. It accepts:
- A JQ expression to extract the key from the parent object
- Optional object for field arguments
Basic Usage:
resolver Team.employees =
|> loader(.parent.id): EmployeesByTeamLoader
With Field Arguments:
resolver Team.employees =
|> loader(.parent.id, { limit: .args.limit, offset: .args.offset }): EmployeesByTeamLoader
How it works:
- The loader batches keys from multiple parent objects
- The loader pipeline receives:
{ keys: [1, 2, 3], args: {...}, context: {...} } - The pipeline must return a map:
{ "1": [...], "2": [...], "3": [...] }
Example Loader Pipeline:
pipeline EmployeesByTeamLoader =
|> pg([(.keys | map(tostring) | join(",")), (.args.limit // 10)]): `!raw
SELECT json_object_agg(sub.team_id, sub.employees)
FROM (
SELECT team_id, json_agg(...) AS employees
FROM employees
WHERE team_id::text = ANY(string_to_array($1, ','))
GROUP BY team_id
) sub
`
See GraphQL Middleware for complete DataLoader documentation.
Setting up SQL parameters:
GET /users/:id/orders
|> jq: `{ sqlParams: [.params.id] }`
|> pg: `SELECT * FROM users WHERE id = $1`
|> jq: `{ sqlParams: [.data.rows[0].id] }`
|> pg: `SELECT * FROM orders WHERE user_id = $1`
Pros:
- Explicit and clear what's being set
- Easier to debug intermediate state
Cons:
- Verbose with repeated
jqsteps - Harder to follow data flow
- More lines of code
Same logic with inline arguments:
GET /users/:id/orders
|> pg([.params.id]): `SELECT * FROM users WHERE id = $1`
|> pg([.data.rows[0].id]): `SELECT * FROM orders WHERE user_id = $1`
Pros:
- Concise and readable
- Clear data flow
- Less boilerplate
Cons:
- Slightly less explicit
- May be harder to debug complex expressions
-
Simple Parameter Extraction:
|> pg([.userId, .status]): `SELECT * FROM users WHERE id = $1 AND status = $2` -
Direct URL Building:
|> fetch("https://api.example.com/users/" + (.id | tostring)) -
Straightforward Variable Mapping:
|> graphql({ userId: .targetId }): `query($userId: ID!) { ... }`
-
Complex Data Transformations:
|> jq: `{ sqlParams: [ (.params.id | tonumber), (.query.status // "active"), (.body.filters | keys | map(. + ":" + .value) | join(",")) ] }` |> pg: `SELECT * FROM complex_query ...` -
Reusing Computed Values:
|> jq: `{ normalizedEmail: (.body.email | ascii_downcase | ltrimstr | rtrimstr), hashedPassword: (.body.password | hash("sha256")) }` |> pg([.normalizedEmail, .hashedPassword]): ` INSERT INTO users (email, password_hash) VALUES ($1, $2) ` -
Debugging Complex Logic:
|> jq: `{ intermediate: .complex | calculation }` |> log: `level: debug` |> pg([.intermediate.value]): `SELECT * FROM ...`
Good:
|> pg([.userId, .status]): `SELECT * FROM users WHERE id = $1 AND status = $2`
Avoid:
|> pg([
(.userId | tonumber // 0),
(if .status == "premium" then "active" else "pending" end),
(.metadata | keys | map(. + "=" + (.value | tostring)) | join("&"))
]): `SELECT * FROM ...`
For complex expressions, use a JQ step first to compute the values, then use inline arguments to pass them.
|> pg([(.params.id | tonumber)]): `SELECT * FROM users WHERE id = $1`
|> fetch("https://api.example.com/users/" + (.userId | tostring))
|> pg([.userId, (.query.limit // 10)]): `SELECT * FROM items WHERE user_id = $1 LIMIT $2`
|> pg([.userId]): `SELECT * FROM users WHERE id = $1` @result(user)
|> pg([.data.user.rows[0].dept_id]): `SELECT * FROM departments WHERE id = $1` @result(dept)
When expressions are complex, use parentheses:
|> pg([(.params.id | tonumber), (.query.status // "active")]): `SELECT * FROM ...`
GET /users/:id/full-profile
|> pg([.params.id]): `SELECT * FROM users WHERE id = $1` @result(user)
|> pg([.data.user.rows[0].company_id]): `SELECT * FROM companies WHERE id = $1` @result(company)
|> pg([.params.id]): `SELECT * FROM orders WHERE user_id = $1` @result(orders)
|> jq: `{
user: .data.user.rows[0],
company: .data.company.rows[0],
orders: .data.orders.rows
}`
GET /search
|> jq: `{
filters: (if .query.category then [.query.category] else [] end)
}`
|> pg([.query.term] + .filters): `
SELECT * FROM products
WHERE name LIKE $1
${if .filters | length > 0 then " AND category = $2" else "" end}
`
GET /data/:source/:id
|> dispatch
case @env(production):
|> fetch("https://prod-api.example.com/" + .params.source + "/" + .params.id)
case @env(development):
|> fetch("https://dev-api.example.com/" + .params.source + "/" + .params.id)
If inline arguments don't match the expected type, middleware will fail with an error:
# This will fail if .userId is not a number
|> pg([.userId]): `SELECT * FROM users WHERE id = $1`
Solution: Use type conversion:
|> pg([(.userId | tonumber)]): `SELECT * FROM users WHERE id = $1`
If a parameter is missing or null, the middleware may fail:
# This will fail if .userId is null
|> pg([.userId]): `SELECT * FROM users WHERE id = $1`
Solution: Provide defaults or validate first:
|> pg([(.userId // 0)]): `SELECT * FROM users WHERE id = $1`
Or use validation middleware:
|> validate: `{ userId: number }`
|> pg([.userId]): `SELECT * FROM users WHERE id = $1`
- pg Middleware - PostgreSQL integration with parameter binding
- fetch Middleware - HTTP requests with dynamic URLs
- graphql Middleware - GraphQL queries with inline variables
- DSL Syntax - Complete syntax reference
- Routes & Pipelines - Pipeline composition patterns