Middleware

Middleware offers powerful functionality to control the behavior of your APIs at pivotal points of execution.

All plans include one instance of middleware at no charge.

Our Scale and higher plans include unlimited middleware.

What is middleware?

Middleware are separate pieces of logic that you build in Xano that can run before your API executes (even before input validation) or after your function stack is finished executing but before your API delivers its response.

Middleware can be applied at an API, API group, or workspace level. This means that you can apply the same middleware functionality to multiple API endpoints, or even your entire application, in one swing. You also have the ability to customize the middleware application at an API or API group level, meaning that if you want to apply your middleware to your entire application except a certain API or group, you can do that too.

Middleware offers the same functionality that any other function stack can utilize.

Middleware Availability

Middleware is available in both free and paid versions.

Free Version

All of our paid plans can utilize the free version of Middleware. In the free version, you can create one Middleware function stack to assign as you see fit across your workspace.

The paid version of Middleware is included with our Scale plan. The premium version allows for unlimited creation and assignment of Middleware on APIs, functions, or tasks. Additional defaults are supported at the API group level, which can either be customized or inherited from workspace defaults.

If your plan includes the Compliance Center, you'll also be able to access reporting on middleware usage from there.

How does middleware work?

Types of Middleware

Pre-Middleware

Pre-middleware executes before any input validation takes place. For example, if you have one of your inputs built in such a way that it requires a minimum string length, your pre-middleware won't be aware of this.

Post-Middleware

Post-middleware executes after the function stack ends, but before the API delivers a response. The output of the middleware can be merged into the response your API generates, or replace it entirely.

Building Middleware

Middleware can be found in your Library from the left-hand navigation.

Click the 'Add Middleware' button in the top-right corner to add new middleware to your workspace. Give your Middleware a name, a description, and any tags you'd like to apply, and proceed.

Once you've added your middleware, you can begin building your logic, just like any other function stack. There are a couple of key differences to be aware of, however.

  • Inputs

    • Middleware inputs are static and can not be changed. They will automatically contain the variables coming from the parent object. This means that for pre-middleware, the parent object will provide the inputs contained in the API request. For post-middleware, this will be your API response.

    • When referencing those inputs, you will do so from inside the vars variable. So, if you send an input labeled text, you'd reference this inside a middleware stack using vars.text

    • The type contains whether this is running as pre or post middleware, and can not be changed.

  • Response

    • Middleware responses have the option to either merge or replace the response in the parent object.

      • Merge means that middleware will generate its response and merge it with the response of the parent object. For example, if my API contains an input called text and my pre-middleware generates a variable called number, and my response type is set to merge, we could use a Get All Input function to retrieve those additional inputs. Merge also means that any items in the response that have the same name as your inputs will be replaced in the process, as you can not have two keys in a JSON object of the same name.

      • Replace means that your middleware will essentially ignore all of the inputs given to it when it generates a response. For example, if I am building pre-middleware, and I send it an input called 'text', and it does not deliver that in the response, that 'text' input will no longer be available in the API's function stack.

  • Exception Preferences

    • Silent ignores errors thrown in middleware and allows the API to return the response without returning the error encountered

    • Rethrow allows post-middleware to execute for the purposes of error logging in your pre-middleware

    • Critical halts all execution if an error occurs

For this example, we'll be using the following middleware:

...with the following API:

The middleware is responsible for checking the 'text' API to determine if it has a value of "Hello" before allowing access.

After you've built your middleware, you're ready to apply it.

Applying Middleware

Applying Middleware to your Entire Workspace

Head to your workspace dashboard, click the top-right icon and choose Middleware.

From the panel that opens, you can add PRE or POST middleware. For this example, we'll be adding PRE middleware.

We've added our PRE middleware. From this same panel, we can also use the toggle on the right-hand side to quickly enable or disable middleware, or delete it from the list entirely.

Navigating back to our API, we can now see an indicator above our inputs that we have 1 Pre-Middleware applied.

When we run this API with the input "Hello", everything will execute as expected with no errors.

When we run this API with the input "Goodbye", we can see that the precondition built into the middleware halts execution and returns the error message we selected.

Because we applied this middleware at a workspace level, this same input validation will run across all of our API endpoints.

Applying Middleware to an API Group or Single API

Middleware applications are inherited from their parent object. This means that any middleware you apply at a workspace level will be populated down to each API group and API. You do have the ability to customize the middleware on API groups and individual APIs for more granular control over the middleware that applies to that object.

Why use Middleware?

Flexibility

Middleware can be much more flexible in certain scenarios, such as input validation. While Xano currently offers several basic filters for input validation, such as enforcing a certain number of characters, middleware offers the power of an entire function stack to not only validate those inputs in new ways, but even transform and manipulate them, should the need arise.

Build Efficiency

One of the key benefits to using middleware, as opposed to using a custom function for example, is the methods at which middleware can be applied. Having the ability to insert both PRE and POST middleware in your function stacks offers more versatile and controlled execution of the logic contained within them.

Not only that, Middleware can also be applied at a workspace or API group level, giving you a one-swing approach to applying middleware to all of your API endpoints at once, instead of having to go through each API one at a time to apply a custom function.

Middleware Examples

Enabling a 'banned' flag in your user table to handle problematic users

Scenario

We have an active application with a number of users onboarded. One of our users is exhibiting some problematic behavior, and we want to block access to that user while the appropriate members of our teams works with them to resolve the issue. When we designed our database, we did not include any sort of functionality to ban or restrict access to our users, and we do not have any way to restrict access to them in a quick and orderly fashion.

Key Concepts

Solution

Use a pre-middleware function to check a 'banned' flag in our user database to determine whether or not our APIs should deliver a response.

Step-by-step

This is the schema of our user table, currently.

{
    "id": integer
    "created_at": timestamp
    "username": text
    "password": password
}

We would first start by adding a new field, labeled 'banned' to the database, and set it to True for the user in question.

Now, let's head to our Middleware and begin building the stack to check if the user making the request is currently banned from the platform.

In this function stack, we're first getting the record of the authenticated user by referencing the auth.id variable. We then utilize a Precondition function to check if that 'banned' flag = false. If it's false, that means the user is authorized. However, if it is true, that means the precondition will halt execution and return an error message that we define.

Once we've finished building our function stack, we can head to our workspace dashboard, and navigate to the Middleware panel using the top-right corner menu, and apply our new middleware application-wide.

We're almost done! We now need to make sure that our Signup API does not execute this middleware, because there is no authenticated requests to our Signup API.

By choosing 'Customize', we've told Xano that we want to explicitly define the middleware that runs when this API is called -- in this case, none at all.

Just make sure the middleware and the Signup API have been published, and we're all done! All of our APIs, minus the ones we choose (in this case, Signup), will check the authenticated user to see if they have been restricted or banned, and return an error message if so.

Takeaways

In just a few minutes, we've made the necessary changes to enable the ability to block certain users from accessing our application without the need to remove them from our database, and without having to modify each API individually or rely on front-end processing.

Apply detailed logging across your entire application

Scenario

You've built your application and are ready to deploy -- but, hold on! You forgot to implement logging, and your launch date is coming up fast! What can we go?

Key Concepts

You'll need to understand at a base level the following Xano features to effectively use this example.

Solution

We can utilize PRE and POST Middleware to quickly deploy logging across all of our APIs in just a few simple steps.

Execution

Let's start by adding a new database table to hold all of our logged data. Here's what the schema looks like for this example.

{
    "id": integer
    "created_at": timestamp
    "api": text
    "querystring": text
    "inputs": json
    "response": json
    "user_id": integer
    "uid": text
}

Now, we can create our middleware. Head to your Middleware library and add a new middleware. This one, we'll call logging-start.

In our middleware, we'll add a record to our logging table with the following information.

{
    "api": env variable: $request_uri
    "querystring": env variable: $request_querystring
    "inputs": variable: vars
    "response": empty
    "user_id": variable: auth.id
    "uid": $http_headers.X-Request-Id
}

This will use the headers of the request to populate most of the data, along with the inputs given and the ID of the authenticated user. The request ID is a key piece here, as we'll use it to edit the same record in our post middleware.

In our response, just return the vars variable as self.

Head back to your Middleware library, and let's add another middleware. This time, we'll call it logging-end.

In this middleware, we'll use an edit record step to edit the record created in our pre-middleware to populate it with the response the API returned.

The field_name will be our uid field, and the field_value will be the X-Request-Id from our $http_headers environment variable.

The only field we'll be writing is response, which is just populated with the variable vars. Remember, that vars variable contains all of the data populated in the API's response, so that's what we'll be storing in the edited record.

We do not need our logging-end middleware to return a response, so we'll just delete what's there, and we're all done!

Now, we're ready to apply our middleware. We can apply our new pre and post middleware to our entire workspace, a specific API group, or a single API. As requests are made to the API, a new log record is created, and edited, containing all of the data we might need to debug.

Here's an example of a record added by using this example.

Takeaways

In just a few minutes, we were able to build and deploy logging across our entire application, or to any specific APIs that we choose. We can now utilize this data for anything from troubleshooting long-running issues, to tracking user journeys, and more. If we ever wanted to expand on the data being collected, all we have to do is update our database table and middleware.

Last updated