UNPKG

@loopback/docs

Version:

Documentation files rendered at [https://loopback.io](https://loopback.io)

221 lines (174 loc) 5.78 kB
--- lang: en title: 'Extending request body parsing' keywords: LoopBack 4.0, LoopBack 4, Node.js, TypeScript, OpenAPI sidebar: lb4_sidebar permalink: /doc/en/lb4/Extending-request-body-parsing.html --- ## Parsing requests LoopBack 4 uses the `Content-Type` header and `requestBody` of the OpenAPI spec to parse the body of http requests. Please see [Parsing requests](./Parsing-requests.md) for more details. The `@loopback/rest` module ships a set of built-in body parsers: - `json`: parses the http request body as a json value (object, array, string, number, boolean, null) - `urlencoded`: decodes the http request body from 'application/x-www-form-urlencoded' - `text`: parses the http request body as a `string` - `stream`: keeps the http request body as a stream without parsing - `raw`: parses the http request body as a `Buffer` To support more media types, LoopBack defines extension points to plug in body parsers to parse the request body. LoopBack's request body parsing capability can be extended in the following ways: ## Adding a new parser To add a new body parser, follow the steps below: 1. Define a class that implements the `BodyParser` interface: ```ts /** * Interface to be implemented by body parser extensions */ export interface BodyParser { /** * Name of the parser */ name: string | symbol; /** * Indicate if the given media type is supported * @param mediaType - Media type */ supports(mediaType: string): boolean; /** * Parse the request body * @param request - http request */ parse(request: Request): Promise<RequestBody>; } ``` A body parser implementation class will be instantiated by the LoopBack runtime within the context and it can leverage dependency injections. For example: ```ts export class JsonBodyParser implements BodyParser { name = 'json'; private jsonParser: BodyParserMiddleware; constructor( @inject(RestBindings.REQUEST_BODY_PARSER_OPTIONS, {optional: true}) options: RequestBodyParserOptions = {}, ) { const jsonOptions = getParserOptions('json', options); this.jsonParser = json(jsonOptions); } // ... } ``` See the complete code at https://github.com/loopbackio/loopback-next/blob/master/packages/rest/src/body-parsers/body-parser.json.ts. 2. Bind the body parser class to your REST server/application: For example, ```ts server.bodyParser(XmlBodyParser); ``` The `bodyParser` api binds `XmlBodyParser` to the context with: - key: `request.bodyParser.XmlBodyParser` - tag: `request.bodyParser` Please note that newly added body parsers are always invoked before the built-in ones. ### Contribute a body parser from a component A component can add one or more body parsers via its bindings property: ```ts import {createBodyParserBinding} from '@loopback/rest'; export class XmlComponent implements Component { bindings = [createBodyParserBinding(XmlBodyParser)]; } ``` ### Customize parser options The request body parser options is bound to `RestBindings.REQUEST_BODY_PARSER_OPTIONS`. To customize request body parser options, you can simply bind a new value to its key. Built-in parsers retrieve their own options from the request body parser options. The parser specific properties override common ones. For example, given the following configuration: ```ts { limit: '1MB' json: { strict: false }, text: { limit: '2MB' }, /** * Validation options for AJV, see https://github.com/epoberezkin/ajv#options * This setting is global for all request body parsers and it cannot be * overridden inside parser specific properties such as `json` or `text`. */ validation: {nullable: true}, } ``` The json parser will be created with `{limit: '1MB', strict: false}` and the text parser with `{limit: '2MB'}`. Custom parsers can choose to have its own `options` from the context by dependency injection, for example: ```ts export class XmlBodyParser implements BodyParser { name = 'xml'; constructor( @inject('request.bodyParsers.xml.options', {optional: true}) options: XmlBodyParserOptions = {}, ) { ... } // ... } ``` ### Extend AJV with custom keywords and formats In addition to configure AJV options via the `validation` property backed by `RestBindings.REQUEST_BODY_PARSER_OPTIONS`, custom Ajv keywords and formats can also be contributed by bindings. #### Contribute a keyword ```ts import {AjvKeyword, RestTags} from '@loopback/rest'; ctx .bind<AjvKeyword>('ajv.keywords.smallNumber') .to({ name: 'smallNumber', // name of the keyword type: 'number', validate: (schema: unknown, data: number) => { // The number is smaller than 10 return data < 10; }, }) .tag(RestTags.AJV_KEYWORD); ``` This enables Ajv to use a schema like `{type: 'number', smallerNumber: true}`. #### Contribute a format ```ts import {AjvFormat, RestTags} from '@loopback/rest'; ctx .bind<AjvFormat>('ajv.formats.int') .to({ name: 'int', // Name of the format type: 'number', validate: (data: number) => { // The number does not have a decimal point return !String(data).includes('.'); }, }) .tag(RestTags.AJV_FORMAT); ``` Now Ajv can understand a schema like `{type: 'number', format: 'int'}`. ## Replace an existing parser An existing parser can be replaced by binding a different value to the application context. ```ts class MyJsonBodyParser implements BodyParser { // ... } app.bodyParser(MyJsonBodyParser, RestBindings.REQUEST_BODY_PARSER_JSON); ``` ## Remove an existing parser An existing parser can be removed from the application by unbinding the corresponding key. For example, the following code removes the built-in JSON body parser. ```ts app.unbind(RestBindings.REQUEST_BODY_PARSER_JSON); ```