Serving your first API
TutorialOperationsSince version 0.1.0
Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.
Goal: route a declared operation, with no identity and no quota, to check the chain end to end.
Command
cat > flexway.yaml <<'YAML'
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://host.docker.internal:8081 # your API; in a container, localhost is the gateway itself
protocol: http
timeout: 3s
YAML
flexway validate # offline, no cluster needed
docker run --rm --name flexway -p 8080:8080 -p 9090:9090 \
--add-host=host.docker.internal:host-gateway \
-v "$PWD:/etc/flexway:ro" \
ghcr.io/flexwiz/flexway:<version>An image tag carries the release's v (ghcr.io/flexwiz/flexway:v1.0.0). The gateway serves
traffic on 8080 and its admin surface on 9090; the openapi.yaml sits next
to flexway.yaml, and every operation in it carries an operationId - an operation without one is
refused at load, since quotas, keys and audit name operations by it.
Check: an operation declared in the OpenAPI answers. An undeclared route answers 404 in
application/problem+json - not because it is forbidden, because it does not exist.
Any operation without
x-flexway-upstreamuses thedefaultupstream. That is what lets you start with four lines.
Path templates. A segment can mix parameters and text, as OpenAPI allows: /reports/{year}.pdf,
/api/v{n}/status, /tiles/{z}-{x}-{y}.png, /tasks/{id}:cancel. Each parameter takes at least one
character and stops at the first text that follows it (/files/{name}.{ext} on archive.tar.gz:
name = archive, ext = tar.gz). Precedence: a literal segment (/reports/latest.pdf), then
the mixed segment with the most text, then the bare parameter (/reports/{id}). Refused at load,
naming the operation: two parameters side by side ({x}{y}), a lone brace, and two mixed segments of
the same rank that can read the same text (a{x} and {x}a).
Before starting, the same check runs offline - no cluster, no network:
flexway validate
# or, without a checkout - the validator ships in every published image (signed, same digest):
docker run --rm -v "$PWD:/etc/flexway:ro" ghcr.io/flexwiz/flexway:<version> validate -v
docker run --rm -v "$PWD:/w:ro" ghcr.io/flexwiz/flexway:<version> validate --config /w/flexway.yaml --contract /w/next.yamlExit code 0 when the profile is accepted, 1 with the field at fault when it is refused, 2 on
a misused command line. --contract validates another OpenAPI document against the same
configuration, in place of contracts.openapi.
It replays exactly what the service does at start - same loader, same refusals. A
validator that reimplemented the checks would eventually disagree with the thing it
validates. One deliberate exception: a secret reference ("${VAR}", file:/...) is checked for
its form, not resolved - a pipeline does not hold the production secrets. An unset variable is
found at start, which refuses to serve and names it.
Tuning timeouts. Three budgets, with defaults that suit an ordinary API - write them only to depart from them:
server:
timeouts:
connect: 5s # establishing the upstream connection
read: 60s # silence tolerated between two bytes - this, and only this, bounds a stream
total: 30s # the whole exchange, retries included
shutdown:
drain: 20s # on SIGTERM, time left to what is in flightThe most specific wins: server.timeouts.total, then timeout on the upstream, then
x-flexway-timeout on the operation in the contract. A search at 2s and an export at 120s live
side by side without raising everyone's budget.
Check: an operation that runs past its budget answers 504 upstream_timeout at that budget,
not at the client's. An SSE stream that emits at least once per read is never cut, however
long it runs.
Say who answers for an operation, and when it is withdrawn. Two contract extensions, read for the console's catalogue - never on the request path:
info:
title: Orders
version: "1.0.0"
x-flexway-owner: { team: orders-team, contact: "#orders" } # every operation inherits it
paths:
/v1/orders/{id}:
get:
operationId: getOrderV1
deprecated: true
x-flexway-sunset: "2027-06-30" # withdrawal date: requires deprecated: true
x-flexway-owner: { team: legacy-team } # the operation's own winsteam is required, contact optional; 128 characters each. The contact is a team's channel (a
list, a room, a page), never a person: every reader of the console sees it. The date is written
YYYY-MM-DD. In gRPC, in the method's option:
rpc ListPayouts(Req) returns (Resp) {
option deprecated = true; // Protobuf's native option, read as deprecated
option (flexway.v1.operation) = { owner: { team: "payments-team" } sunset: "2027-03-01" };
}Check: flexway validate refuses a date without deprecation ("is set on an operation that is not
deprecated"), a malformed date, an owner without team or with an unknown key; the admin /config
publishes owner and sunset in catalog. An operation without an owner loads: the catalogue's
compliance reports it. The Sunset and Deprecation headers towards callers come with a later change.
Available since 1.2.0.