Papupata Documentation

Guide: Migrating to papupata 2.x

Overview

There are no major incompatibilities from 1.x to 2.x, but there may well be a few things you'll have to change as you upgrade. This guide covers those, as well as giving a brief overview of the new additions.

Table of contents

Breaking changes

As this is a major version upgrade, there are some breaking changes.

autoImplementAllAPIs default to true

The default value for the configuration option autoImplementAllAPIs is now true. This change will affect you if you have not used the setting, use papupata on the server and have routes declared with papupata but not implemented with it.

The result is that such APIs will start returning 501, unless express (or something else) handles them before papupata gets to.

For a workaround, the easiest solution is to explcitly set the setting to false.

API.configure({
  ...otherOptions,
  autoImplementAllAPIs: false,
})

You could also update your APIs to either work with this setting, or set the disableAutoImplement option to true on the affected routes.

Imports from withing papupata package have been restructured

In papupata 1.x there were files that were imported from papupata/dist; that is no longer supported and such imports must be updated according as follows.

Old importNew import
papupata/dist/main/fetchAdapterpapupata/adapters/fetch
papupata/dist/main/invokeImplementationAdapterpapupata/adapters/invokeImplementation
papupata/dist/main/request-promise-adapterpapupata/adapters/requestPromise
papupata/dist/main/supertestAdapterpapupata/adapters/supertest
papupata/dist/main/supertestInvokerpapupata/invokers/supertest
papupata/dist/main/testInvokerpapupata/invokers/test
ES6 support is now required

Typescript is now configured to use ES6 as its compilation target, as that is required for Error to be subclassed properly with ES classes.

If this turns out to be a problem for you, please submit an issue and we can look in to creating a browser build that still compiles to ES5.

apiUrlParameters on a declared API has had its type changed

The apiUrlParameters property has had its type changed. It used to contain arrays of strings, but now it contains TypeMapping objects instead. It is not very likely that you've used this field, and if you have it's most likely in middleware.

For existing APIs you can get the old format with:

const oldStyleParameters = {
  params: Object.keys(api.apiUrlParameters.params),
  query: Object.keys(api.apiUrlParameters.query),
  optionalQuery: Object.keys(api.apiUrlParameters.optionalQuery),
  boolQuery: Object.keys(api.apiUrlParameters.boolQuery),
}

Do note that for APIs that use the new style typed query parameters there will be nothing in boolQuery even if there are boolean parameters.

Presence of required query parameters is verified

Papupata 1.x allowed you to declare required query parameters, and while the types indicated they were required, no effort was made to ensure they were actually present. This is no longer the case, and missing required query parameters are considered a validation error.

By default this means that papupata will throw a PapupataValidationError, which can be handle by express middleware, but there is no way get to the actual route implementation or any middleware with missing query parameters.

The only fix is to move all of the optional query parameters from query to optionalQuery, where they should've been all along.

Features supported by qs but not by papupata

Requests typically have their query parameters parsed by qs, and papupata has just taken whatever values qs has produced and assumed it's all good. This is no longer the case, and papupata ensures that everything in the request query conforms to what has been declared in the API declaration (albeit additional fields can still exist).

For the most part this should not be a problem, but if you have done some interesting things like passing arrays with "as any" you'll probably have to make some changes.

Request adapters can receive undefined as the body

In papupata 1.x request adapters always got a body, which normally was an object. The only situation where it might not be an object was if you specifically invoked an API that had a non-object body that was passed to papupata.

Now, if the body would be an empty object, undefined is passed to request adapters instead. This makes it easier not to accidentally send empty bodies with GET requests, for example, while still allowing them to be present for DELETE requests, if required. (while DELETE is not meant to have a body, it is not entirely unknown for it to be used with one)

You might have to update your custom request adapters to deal with the undefined body, with something along the lines of

const actualBody = body ?? {}

As this does easily cause the API implementation not to receive any body even if it has been declared to be an object, papupata's own middleware makes the body be an empty object if it would otherwise be undefined.

Middleware is applied to automatically implemented routes

Routes that are are automatically implemented (to return a 501) thanks to autoImplementAllAPIs being set to true (which is now the default) are subject to any middeware set up on the papupata API declaration itself.

This allows logging middleware etc. to work properly even when the APIs are not implemented, but it does mean that the middleware is called in situations where it was not before.

Deprecated functionality

Some old features are still supported, but considered deprecated and should be avoided.

Arrays for declaring query and path parameters

In papupata 1.x all path and query parameters were declared as const arrays. In 2.x it is now possible, and recommended that you instead use TypeMapping objects to declare the parameters. The main advantages are never again forgetting the as const and being able to use a variety of types for the parameters instead of just strings.

const api = API.declareGetAPI('/path/:id')
  .params({ id: Number })
  .query({ query: String })
  .optionalQuery({ limit: Number, startFrom: Date}})
  .response<any>()
queryBool

Declaring boolean query parameters with queryBool is no longer recommended, instead they should be declared as normal or optional query parameters with boolean type.

const api = API.declareGetAPI('/path')
  .query({ myParam: Boolean }})
  .response<any>()

Do however not that this it not a perfect match for the old behavior; in 1.x any values other than the string "true" were treated as false, whereas in 2.x the only permitted values are the string "true", "false" and an empty string (which also stands for false). If the parameters is optional, undefined is also valid value but remains undefined instead of becoming false.

Configuration option makeRequest is now requestAdapter

In order to be more consistent with the documentation, the configuration option makeRequest is now instead requestAdapter. The old name can still be used, however.