Publish queries (http and websockets)

You might decide that instead of submitting queries directly to Flow, or using one of our SDKs to execute a query programmatically, you’d rather just publish the query as an HTTP(s) endpoint.

Saved queries

Any queries committed to your taxonomy that have an @HttpOperation annotation will automatically be converted into an HTTP query.

For example:

import taxi.http.HttpOperation

@HttpOperation(url = '/api/q/movies/{movieId}', method = 'GET')
query getMoviesAndReivews(@PathVariable("movieId") movieId: MovieId) {
  given { movieId }
  find {
    title : MovieTitle
    reviewScore : ReviewScore
  }
}

Any declared query that’s in a Taxi project published to Flow can be converted into an HTTP operation.

HttpOperation annotation

Queries need to have an @HttpOperation annotation:

Parameter Description

url

The URL the query is exposed under. It must start /api/q. Any other query URLs are ignored

method

The HTTP method to respond to, one of GET,POST,PUT, or DELETE

Variables can be defined in your path using {myVar}, which are then made available in your query declaration using a @PathVariable annotation.

For security reasons, queries are only exposed under the /api/q endpoint. Any queries where URL in the @HttpOperation annotation starts with something other than /api/q are ignored.

Declare the query

Named queries are defined using a query syntax:

Path Variables

// Declares a query named getMoviesAndReviews`
query getMoviesAndReviews(
   // Define variables to be passed in
   @PathVariable("movieId") movieId: MovieId
) { ... }

Request Body

For a POST query, the request body is also available:

query doASearch(@RequestBody searchRequest: SearchRequest) {
 ...
}

Given clause

If you’ve written Taxi queries before, you’re used to using the given clause to provide data into the query.

For saved queries, that data is often passed as a variable, so the syntax is slightly different:

// Not a saved query, given clause has a value:
given { movieId: MovieId = 123 }
find { ... }

// A saved query, the movieId comes from the params:
query findMoviesAndReviews(@PathVariable(...) movieId: MovieId) {
  given { movieId } // exposes the parameter into the query as a fact.
  find { ... }
}

Stream results with server-sent events (SSE)

To receive query results as a continuous stream of server-sent events, set the Accept header to text/event-stream.

This method can be applied to both standard Request/Response queries and streaming queries.

When using SSE, especially with queries returning multiple records (e.g., database queries), the time-to-first-byte is generally faster. This efficiency is due to records being transmitted as soon as they become available, unlike the standard approach where all records are delivered together at the end of the request.

Example request using curl:

curl -X GET 'http://localhost:9021/api/q/streamingTest' \
  -H 'Accept: text/event-stream;charset-UTF-8'

Saved streams

Streaming queries can be exposed over either HTTP (using server-sent events), or Websocket (or both).

Saved streams are executed in the background. By connecting to the output stream, you can observe the results.

Note that when you observe a result feed from a streaming query, results are published from the point the request is received. Previous results are not published on result stream.

You can expose streaming queries on both server-sent event endpoints and Websockets - simply add both the annotations.

Publish on WebSocket

To publish a query over a websocket, annotate the query with the WebsocketOperation annotation, declaring the path:

For security reasons, websocket streams are only exposed under the /api/s path. Any queries where path in the @WebsocketOperation annotation starts with something other than /api/s are ignored.
@WebsocketOperation(path = '/api/s/newReleases')
query getNewReleaseAnnouncements {
  stream { NewReleaseAnnouncement } as {
    title : MovieTitle
    reviewScore : ReviewScore
  }[]
}

Publish as server-sent events

Streaming queries can also be published over HTTP by declaring a @HttpOperation, and then requesting a text/event-stream response:

@HttpOperation(url = '/api/q/newReleases', method = 'GET')
query getNewReleaseAnnouncements {
  stream { NewReleaseAnnouncement } as {
    title : MovieTitle
    reviewScore : ReviewScore
  }[]
}

Once this is published, results can be streamed by requesting a text/event-stream:

curl -X GET 'http://localhost:9021/api/q/newReleases' \
  -H 'Accept: text/event-stream;charset-UTF-8'

Authentication to Query Endpoints

When Flow has been deployed with Identity Provider integration enabled, the query endpoints are secured using OAuth2.0. In order to access the query endpoints, you must provide a valid access token in the Authorization HTTP header. The access token is obtained by authenticating with the Identity Provider. For more information on how to obtain an access token, see Obtain an Access Token.

Instead of executing with the requesting users permissions (as request / response queries do), persistent streaming queries execute with a system account - the Executor user.

Configuring the Executor user

flow.security.openIdp.executorRole prefixed parameters are required to configure the Executor user, see this Docker Compose file. Some of the parameters are pre-configured, see potential modification of security pre-configuration.

Observers vs Executors

Persistent streams are always executed under the permissions of the Executor user. However, these streams can also be observed by other users, through published http or websocket endpoint.

In this scenario, policies are applied twice:

  • First, the stream is executed using the permissions of the Executor user

  • Then, when being observed, the results of the stream are then re-evaluated using the permissions of the user observing the stream As a result, the observed output may differ from the actual data being emitted by the stream.

Live reload

Any changes made to the queries are automatically deployed.

  • When developing locally, this is as soon as you save a file

  • In production, when using a Git repository, as soon as changes are merged, they’re deployed on the next poll (typically a couple of minutes)

OpenAPI

Flow automatically creates an OpenAPI spec for any query endpoints it serves.

The OpenAPI specs are available at /api/q/meta/{nameOfQuery}/oas.

For example, a query defined as query findMoviesAndReviews(...) would have an OpenAPI spec available at /api/q/meta/findMoviesAndReviews/oas