The Sequence
generated before @loopback/rest@6.0.0
comes with hard-coded
actions as follows:
import {inject} from '@loopback/core';
import {
FindRoute,
InvokeMethod,
InvokeMiddleware,
ParseParams,
Reject,
RequestContext,
RestBindings,
Send,
SequenceHandler,
} from '@loopback/rest';
const SequenceActions = RestBindings.SequenceActions;
export class MySequence implements SequenceHandler {
/**
* Optional invoker for registered middleware in a chain.
* To be injected via SequenceActions.INVOKE_MIDDLEWARE.
*/
@inject(SequenceActions.INVOKE_MIDDLEWARE, {optional: true})
protected invokeMiddleware: InvokeMiddleware = () => false;
constructor(
@inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
@inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
@inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
@inject(SequenceActions.SEND) public send: Send,
@inject(SequenceActions.REJECT) public reject: Reject,
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const finished = await this.invokeMiddleware(context);
if (finished) return;
const route = this.findRoute(request);
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
this.reject(context, err);
}
}
}
The legacy Sequence
is now deprecated but it should continue to work without
any changes.
If you have never customized src/sequence.ts
generated by lb4 app
, or you
have only modified the code by adding
authenticate
action per our docs, you
can simply migrate to the middleware based sequence by replacing the content of
src/sequence.ts
with the code below.
import {MiddlewareSequence} from '@loopback/rest';
export class MySequence extends MiddlewareSequence {}
If you have other actions in your sequence, e.g., complex error handling per end-point, you’ll have to write a Middleware to wrap your action and register it with the middleware chain for the middleware-based sequence.
This can be achieved by creating a Middleware in
src/middlewares/error-handler.middleware.ts
with the code below:
import {inject, injectable, Next, Provider} from '@loopback/core';
import {
asMiddleware,
HttpErrors,
LogError,
Middleware,
Response,
MiddlewareContext,
RestBindings,
RestMiddlewareGroups,
} from '@loopback/rest';
@injectable(
asMiddleware({
group: 'validationError',
upstreamGroups: RestMiddlewareGroups.SEND_RESPONSE,
downstreamGroups: RestMiddlewareGroups.CORS,
}),
)
export class ErrorHandlerMiddlewareProvider implements Provider<Middleware> {
constructor(
@inject(RestBindings.SequenceActions.LOG_ERROR)
protected logError: LogError,
) {}
async value() {
const middleware: Middleware = async (
ctx: MiddlewareContext,
next: Next,
) => {
try {
return await next();
} catch (err) {
// Any error handling goes here
return this.handleError(ctx, err);
}
};
return middleware;
}
handleError(context: MiddlewareContext, err: HttpErrors.HttpError): Response {
// We simply log the error although more complex scenarios can be performed
// such as customizing errors for a specific endpoint
this.logError(err, err.statusCode, context.request);
throw err;
}
}
then bond to the application src/application.ts
by:
import {ErrorHandlerMiddlewareProvider} from './middlewares';
//...
constructor(options: ApplicationConfig = {}) {
...
this.add(createBindingFromClass(ErrorHandlerMiddlewareProvider));
}
// ...
As part of the legacy action-based sequence, the invoke
function takes route
and args
as input parameters and returns result
.
const route = this.findRoute(request);
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
The corresponding middleware for invokeMethod
looks like the following. It now
uses the context
to retrieve route
and params
instead. The return value is
also bound to RestBindings.Operation.RETURN_VALUE
to expose to other
middleware in the chain, as illustrated below.
...
export class ErrorHandlerMiddlewareProvider implements Provider<Middleware> {
...
async value() {
const middleware: Middleware = async (
ctx: MiddlewareContext,
next: Next,
) => {
try {
// Locate or inject input parameters from the request context
const route: RouteEntry = await ctx.get(RestBindings.Operation.ROUTE);
const params: OperationArgs = await ctx.get(RestBindings.Operation.PARAMS);
const retVal = await this.invokeMethod(route, params);
// Bind the return value to the request context
ctx.bind(RestBindings.Operation.RETURN_VALUE).to(retVal);
return await next();
} catch (err) {
// Any error handling goes here
....
}
};
return middleware;
}
....
}