Advanced template functions for dynamic endpoints

The Template functions section of the Advanced templates docs explains functions that help you create more advanced dynamic templates. On this page, you'll learn about how these templates can be used to create dynamic endpoints with Tinybird.

## Prerequisites

Before continuing, make sure you're familiar with template functions and query parameters. You may also find the blog post "Creating an API with dynamic parameters on stock market data" insightful.

The data

This guide uses the ecommerce events data enriched with products. The data looks like this:

Tips and tricks

When the complexity of Pipes and endpoints grows, developing them and knowing what's going-on to debug problems can become challenging. Here are some tricks that we use when using our product for ourselves and for our clients that we think would be useful for you as well:

WHERE 1=1

When you filter by different criteria, given by dynamic parameters that can be omitted, you'll need a WHERE clause. But if none of the parameters are present, you'll need to add a WHERE statement with a dummy condition (like 1=1) that's always true, and then add the other filter statements dynamically if the parameters are defined, like we do in the defined example of this guide.

set

The set function present in the previous snippet lets you set the value of a parameter in a node, so that you can check the output of a query depending on the value of the parameters it takes. Otherwise, you'd have to to publish an endpoint and make requests to its endpoint with different parameters. Using set, you don't have to exit the Tinybird UI while creating an endpoint and the whole process is faster, without needing to go back and forth between your browser or IDE and Postman (or whatever you use to make requests).

Another example of its usage:

You can use more than one set statement. Just put each one on a separate line at the beginning of a node.

set is also a way to set defaults for parameters. If you used set statements to test your endpoint while developing, remember to remove them before publishing your code.

The default argument

Another way to set default values for parameters is using the default argument that most Tinybird template functions accept. The previous code could be rewritten as follows:

Keep in mind that defining the same parameter in more than one place in your code in different ways can lead to inconsistent behaviour. Here's a solution to avoid that:

Using WITH statements to avoid duplicating code

If you are going to use the same dynamic parameters more than once in a node of a Pipe, it's good to define them in one place only to avoid duplicating code. It also makes it clearer knowing which parameters are going to appear in the node. This can be done with one or more statements at the beginning of a node, using the WITHclause.

The WITH clause in ClickHouse supports CTEs. They're preprocessed before executing the query, and they can only return one row (this is different to other databases such as Postgres). This is better seen with a live example:

Documenting your endpoints

Tinybird creates auto-generated documentation for all your published endpoints, taking the information from the dynamic parameters found in the Pipe. It's best practice to set default values and descriptions for every parameter in one place (also because some functions don't accept a description, for example). We normally do that in the final node, with WITH statements at the beginning. See how we'd do it in the last section of this guide.

Hidden parameters

If you use some functions like enumerate_with_last in the example below, you'll end up with some variables (called x, last in that code snippet) that Tinybird will interpret as if they were parameters that you can set, and they will appear in the auto-generated documentation page. To avoid that, add a leading underscore to their name, renaming x to _x and last to _last.

Debugging any query

We have an experimental feature that lets you see how the actual SQL code that will be run on ClickHouse for any published endpoint looks, interpolating the query string parameters that you pass in the request URL. If you have a complex query and you'd like to know what is the SQL that will be run, let us know and we'll give you access to this feature to debug a query.

Now let's explore some of the Tinybird advanced template functions, what they allow you to do, and some tricks that will improve your experience creating dynamic endpoints on Tinybird.

Advanced functions

Most of these functions also appear in the Advanced templates section of our docs. Here we'll provide practical examples of their usage so that it's easier for you to understand how to use them.

defined

The defined function lets you check if a query string parameter exists in the request URL or not.

Imagine you want to filter events with a price within a minimum or a maximum price, set by two dynamic parameters that could be omitted. A way to define the endpoint would be like this:

To see the effect of having a parameter not defined, use set to set its value to None like this:

You could also provide some smart defaults to avoid needing to use the defined function at all:

Array(variable_name, 'type', [default])

Transforms a comma-separated list of values into a Tuple. You can provide a default value for it or not:

To filter events whose type belongs to the ones provided in a dynamic parameter, separated by commas, you'd define the endpoint like this:

And then the URL of the endpoint would be something like https://api.tinybird.co/v0/pipes/your_pipe_name.json?event_types=buy,view

sql_and

sql_and lets you create a filter with AND operators and several expressions dynamically, taking into account if the dynamic parameters in a template it are present in the request URL.

It's not possible to use ClickHouse functions inside the {{ }} brackets in templates. sql_and can only be used with the{column_name}__{operand} syntax. This function does the same as what you saw in the previous query: filtering a column by the values that are present in a tuple generated by Array(...) if operand is in, are greater than (with the gt operand), or less than (with the lt operand). Let's see an example to make it clearer:

You don't have to provide default values. If you set the defined argument of Array to False, when that parameter is not provided, no SQL expression will be generated. You can see this in the next code snippet:

split_to_array(name, [default])

This works similarly to Array, but it returns an Array of Strings (instead of a tuple). You'll have to cast the result to the type you want after. As you can see here too, they behave in a similar way:

One thing that you'll want to keep in mind is that you can't pass non-constant values (arrays, for example) to operations that require them. For example, this would fail:

If you find an error like this, you should use a Tuple instead (remember that {{Array(...)}} returns a tuple). This will work:

split_to_array is often used with enumerate_with_last.

column and columns

They let you select one or several columns from a Data Source or Pipe, given their name. You can also provide a default value.

enumerate_with_last

As the docs say, it creates an iterable array, returning a Boolean value that allows checking if the current element is the last element in the array. Its most common usage is to select several columns, or compute some function over them. We can see an example of columns and enumerate_with_last here:

If you use the defined function around a parameter it doesn't make sense to give it a default value because if it's not provided, that line will never be run.

error and custom_error

They let you return customized error responses. With error you can customize the error message:

And with custom_error you can also customize the response code:

Note: error and custom_error have to be placed at the start of a node or they won't work. The order should be:

  1. set lines, to give some parameter a default value (optional)
  2. Parameter validation functions: error and custom_error definitions
  3. The SQL query itself

Putting it all together

We've created a Pipe where we use most of these advanced techniques to filter ecommerce events. You can see its live documentation page here and play with it on Swagger here.

This is its code:

To replicate it in your account, copy the previous code to a new file called advanced_dynamic_endpoints.pipe locally and run `tb push pipes/advanced_dynamic_endpoints.pipe` with our CLI to push it to your Tinybird account.