Middleware
The generated resolvers only write data that is applicable to this application, but your database may have additional information you want to write.
To add additional data to the database mutations, you can provide middleware
which transform the data before we make the query.
Middleware is also useful for adding additional functionality right before the database query is made - logging is a common use case.
#
Middleware overviewA middleware
is just a function that accepts some data and mutates it, or does some other additional functionality. You may be familiar with this concept if you have ever used a library like Express or Redux.
Middleware is called in a sequential chain. This means if you provide two middleware functions, the first one will be called, and the result of the first one will be passed into the second one. The return value of the last middleware is the final data. This allows you to write small, composable functions to transform your data.
Each middleware is passed a next
function. This function is called when your middleware is complete. Calling next
will trigger the next middleware in your chain.
#
Write middlewareWrite middleware can be used to add/change the data that is written to your database. These middleware functions are called before data is written to your database.
For example, if you had a column in your Documents
table called ProjectId
, you would need to use middleware to write this data since the generated resolvers don't handle this column by default.
To use write middleware, you can provide a writeMiddleware
option to your table entity.
This example will add additional data to the query, and will insert 'some-project-id' into the ProjectId
column.
writeMiddleware
(Array<(info
: Object) => void>) An array of middlewareinfo
(object)info.data
(Object) The data to be transformed. The keys are the column names we will write to, and the value is the data we will write to that column. Any additional properties that you add to this object will also be written to your database.info.type
('create' | 'update') The type of mutation being executed. This allows you to do operation specific logic, such as adding anId
only when we are creating a new entity.info.ctx
(Object) The context of this request, see thecontext
section for more details.info.knex
(Object) The Knex instance allows you to build a custom query. See theBuild custom query in middleware
section below for more details.info.next
(Function) A callback function to be called when your middleware is complete. Must be called withdata
as the first parameter, andctx
as the second. The values you provide are passed into the next middleware function. See examples below.
#
Read middlewareRead middleware can be used to transform/edit data that is read from your database. These middleware functions are called after data is read from your database.
For example, if you are storing XFDF strings in blob storage, you can use read middleware to fetch the contents of the blob.
To use read middleware, you can provide a readeMiddleware
option to your table entity.
readMiddleware
(Array<(info
: Object) => void>) An array of middlewareinfo
(object)info.data
(Object) The read data to be transformed.info.ctx
(Object) The context of this request, see thecontext
section for more details.info.knex
(Object) The Knex instance allows you to build a custom query. See theBuild custom query in middleware
section below for more details.info.next
(Function) A callback function to be called when your middleware is complete. Must be called withdata
as the first parameter, andctx
as the second. The values you provide are passed into the next middleware function. See examples below.
#
Composing middlewareSince middleware are just functions, you can write small, reusable logic and apply middleware to multiple tables. For example, if you wanted to append a custom ID to every new entity, you could do this:
#
Chaining middlewareAs mentioned previously, middleware is called in a chain, with the result of one being passed into the next.
#
Custom query in middlewareWe use Knex to build queries internally. You can use the knex
instance in the middleware params to build your custom query. All the Knex API docs are available here.
#
ContextEach middleware is passed a custom ctx
(context) object. This object can be used to pass data between middleware, and more!
See this guide for more information.
#
Examples#
Simple, detailed example (writing additional data)The above code does not do any transformations to data
, which means only the default data will be written to your database.
The above code will essentially execute this query when a new document is created:
However, if we change our middleware to alter the data
object, we can change the query that is made:
Notice here that we are adding a ProjectId
property to data
. This transforms our query into:
#
Add data only on certain mutation types#
Log all requests#
Async middlewareSince the next middleware is not called until the previous middleware calls next
, you can make your middleware functions asynchronous.
#
With contextContext is extremely useful for setting custom data in your database.
First, set the context data using the setContext
or updateContext
function provided by the client:
Now, you have access to projectId
in all your middleware functions:
#
Blob storageWhen using the snapshots feature, it may be a good idea to store the XFDF strings in a blob to avoid storing large strings directly in the database.
You can combine read and write middleware to achieve this: