Writing Handler Middleware
Similar to express middleware, @simply-openapi/controllers provides a middleware system to process requests. All of the library's functionality is built out in this middleware system, allowing you to provide your own middleware to override or alter behavior as-needed.
Middleware in @simply-openapi/controllers has multiple purposes:
Validate that the request matches the OpenAPI specification
Extract, preprocess, and transform data from the request object for consumption by the operation handlers.
Handle operation handler return values, serializing them, and sending them back with the response
Trapping and handling thrown errors.
The basics of handler middleware
Handler middleware takes the form of an async function that is called with the context of a request. As with express, it is responsible for calling the next handler in the chain. It can then reinterpret the results of that call, either returning it to the next handler up the chain, or handling it directly and returning undefined
to indicate that no further processing is needed.
For example, @simply-openapi/controllers provides this default handler, which is responsible for sending json object responses.
Middleware basics
Middleware functions have a few traits they must abide by:
A middleware function must be declared to take 2 arguments: A context, and a next function.
This must be done at the javascript level so that Function.arguments.length is equal to 2. This is because more advanced middleware forms are differentiated by this count. See Middleware Factories.
A middleware function must return a promise.
In general, a typical middleware will do prepratory work before the call, extract and register data for the controller method arguments, call and await next(), then process the returned result. It will then either forward that result as its own return value, or return undefined
to communicate to up-chain middleware that the result has been handled.
The Middleware Context
The first argument to the middleware is its context, which contains all the information needed to proces the response. Some useful properties are:
req
- The express request.res
- The express response.spec
- The top level OpenAPI specification.method
- The request / operation method.path
- The request / operation path (as it exists in the OpenAPI spec).operation
- The OpenAPI Operation Object for the method of this request.getRequestData
- A method to get data extracted from the request. See Request Data.setRequestData
- A method to set data extracted from the request. See Request Data.
Many more properties and methods are available to further simplify interacting with the request. For a full list, see the documentation for the OperationRequestContext.
Middleware factories
Sometimes, middleware might want to do prepratory work on the method it is bound to. This can involve verifying the validity of the OpenAPI schema or pre-compiling json-schema validators so that the actual request is as fast as possible.
In cases like this, you can provide a middleware factory. A middleware factory is called for every operation that uses it at the time of router creation, and returns a middleware function to use for that operation.
Unlike middleware, a middleware factory function has a single argument that recieves an OperationMiddlewareFactoryContext, and no next function. As with middleware, the function's arguments.length must be equal to 1 for it to be properly detected as a middleware factory function. This factory context contains all the information about the schema, the operation, and the class and method being used to process the request. It also contains a validators
object, which contains various validation function factories for converting OpenAPI SchemaObjects into validators with varying degrees of strictness and data coercion.
See Schema Based Validation for an example.
Writing middleware for request validation
Basic request validation
Request validation can be performed through handler middleware. If a middleware determines that a request is invalid, it should throw an error derived from the http-error
library to propogate the failure up the middleware stack for eventual transmission.
Schema based validation
@simply-openapi/controllers makes heavy use of json-schema based SchemaObjects. Depending on the context, these schemas can be used either strictly for validation, or they can be used to validate, coerce, and provide default values. All of this behavior is driven by AJV, preconfigured to support OpenAPI constructs.
As AJV performs code generation to 'compile' the schemas, middleware that wants to perform validation with it needs to be able to pre-process these schemas to perform the heavy compilation step on startup rather than re-compiling the schema on every request. However, often the schema required is dependent on the particular operation, which must be derived seperately for every operation.
Middleware Factories make this possible.
By default, @simply-openapi/controllers comes with two validators, createStrictValidator
and createCoercingValidator
, but you can modify these or add your own. See OperationMiddlewareFacotryContext for details on these validators, or look to Modifying or adding OpenAPI Schema Validators to modify or add your own.
Middleware for data extraction
Middleware can be used to extract data from the requests to be presented in a processed form to the controller method.
This is done with Request Data:
Middleware for result transmission
Middleware can be used to intercept the result of a controller method. It can transform the results to pass upstream, or it can serialize it directly to the express response.
Default middleware
All of the default behavior of @simply-openapi/controllers is implemented through middleware and middleware factories. The default middleware performs the following in order:
Processes the global and operation-specific
security
OpenAPI directives. Improper requests are denied, and proper requests have their authentication result registered as request data for the@RequireAuthentication
and@BindSecurity
decorators.Processes the path and operation
parameters
OpenAPI directives. Invalid requests are rejected, and values are coerced and registered for the various parameter-centric decorators (@QueryParam
,@PathParam
, and similar).Process the
requestBody
OpenAPI directive. Data is validated and coerced according to its media type, and registered for the@Body
decorator and its variants.If no further middleware has processed the handler result, and the handler result is an instance of the
HandlerResult
class, handling of the result is delegated to that class.If no further middleware has processed the handler result, the handler result is checked to see if it is a plain (non class instance) JSON object. If so, it is trasnformed into a HandlerResult, specifying a content type of "application/json" and setting the status code to 200. This handler result is then passed up the middleware chain.
Last updated