Guide: setting up papupata for servers
Overview
In order to start implementing APIs on the server you need to configure papupata to let it know about its environment. This guide covers the most common cases and gives pointer for less common ones.Table of contents
Prerequisites
Before starting this guide, should know a little bit about API declarations. See Declaring APIs for more details.
The Basics
The one thing that is absolutely essential for implementing APIs, is providing papupata with either an express application or router, which it will declare its routes on.
The configuration itself takes place using the configure method of an API declaration.
import express, {Router} from 'express'
const app = express()
const API = new APIDeclaration()
API.configure({
app
})
const router = Router()
app.use(router)
API.configure({
router
})
Automatic route implementation
There is not configuration as this is enabled by default.
If you are going to implement all, or at least the vast majority of the APIs that have been declared using papupata with papupata, it makes sense to have papupata automatically set up all routes to return 501 Not Implemented until the routes are actually implemented.
This is the default in papupata 2.x and can be turned off by explicitly setting the option to false.
API.updateConfig({
autoImplementAllAPIs: false
})
Having the setting enabled it obvious what is wrong if you try to invoke an API that lacks implementation. There is a more important effect to this as well, as it means that routes are implemented in the order they were declared in, rather than the order they are implemented in. Usually this does not make a difference, but sometimes the routing can be ambiguous, with the order being the deciding factor. Consider this example:
const api1 = API.declareGetAPI('/entries/all').response<any>()
const api2 = API.declareGetAPI('/entries/:id').params({id: String}}).response<any>()
It's quite obvious reading it that the intent is that /entries/all goes to api1, and, say, /entries/123 goes to api2. There is however nothing that inherently says that /entries/all shouldn't be handled by api2. Unless the autoImplementAllAPIs setting is set to true, then you'd have to make sure that implementing api1 takes place before api2 is implemented. With the setting set to true though, it is enough for api1 to be declared before api2, as is the case in the example.
With this setting enabled it is still possible for individual routes to fall back to regular express routing, allowing the implementation to be done in other ways. This is done by having the implementation or a middleware leading to it return papupata.skipHandlingRoute
import {skipHandlingRoute} from 'papupata'
api1.implement(() => skipHandlingRoute)
Papupata 2.x also allows you to declare your desire not to have a route be implemented using papupata in the declarations itself.
const api3 = API.declareGetAPI('/another', {}, { disableAutoImplement: true})
Base URL
API.updateConfig({
baseURL: 'https://www.example.com'
})
Setting up the base url is essential for clients, but it can be useful for servers as well. With a base url set up, you can call the getURL method on the declarations, which can be useful for redirections, callback urls etc.
Just for the purpose of implementing routes it is unnecessary.
Non-root routers
API.updateConfig({
routerAt: '/api'
})
You might find it convenient to set up papupata implementation on an express router that is not at the root of the server. As a common example, you might want to set up the router to be under /api so that its middleware is only applied to API calls.
This is a supported scenario -- all you have to do is add the routerAt option to the configuration to tell papupata where the router is mounted at.
All of the APIs in the declaration to be within the router path -- you cannot have routes at paths where they cannot be implemented
import express, {Router} from 'express'
import {APIDeclaration} from 'papupata'
const API = new APIDeclaration()
const getOne = API.declareGetAPI('/api/getOne').response<any>()
const app = express()
const router = Router()
router.use(authenticationMiddleware)
app.use('/api', router)
API.configure({
router,
routerAt: '/api'
})
Middleware
API.updateConfig({
inherentMiddleware: [myMiddleware1, myMiddleware2]
})
Papupata supports middleware, which can be used on the server to process both requests and responses. For more details see the middleware guide.
Papupata comes with one built-in middleware that changes how undefined responses are handled. Currently an implementation returning undefined is taken as an indication that it takes full responsibility for handling the response.
In practice though, a more likely scenario is that the API has nothing to return, and having returning undefined result in HTTP 204 (no content) would be better. And that is exactly what the middleware does.
Here's an overview of what it actually does:
- If headers have already been sent, it does nothing (this still lets you have APIs that take care of the whole response on their own)
- If status was not explicitly set, it sets it to 204
- It sends a response to the client with no data
import {handleUndefinedResponsesMiddleware} from 'papupata'
API.updateConfig({
inherentMiddleware: [handleUndefinedResponsesMiddleware]
})
api1.implement(() => {}) // results in a 204 response
api2.implement((_req, res) => {res.redirect('/')}) // the redirection works as expected
api3.implement((_req, res) => {res.status(400)}) // status 400, response is sent with no data
api4.implement((_req, res) => {res.send('done')}) // nothing special happens
Conclusion
Now that papupata is set up to respond to requests, the next step should be actually implementing the APIs.
See implementing APIs for how to do exactly that.
If you are concerned with how papupata interacts with your existing express APIs and middleware, you could also take a look at Interacting with express.