This page gives you some pointers on how to handle incoming requests in a way that will reduce the work needed by you as a developer, and create consistent and easy-to-process responses.
For modules that expose HTTP endpoints, the preferred approach is to declare routes in a routes.json file in the module root. This keeps route definitions, permissions, and API metadata in one place.
{
"root": "mymodule",
"routes": [
{
"route": "/action",
"handlers": { "post": "actionHandler" },
"permissions": { "post": ["write:myresource"] },
"meta": {
"post": {
"summary": "Perform an action",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" }
}
}
}
}
},
"responses": { "204": {} }
}
}
}
]
}Then in your module code, use loadRouteConfig and registerRoutes to load and wire up the routes:
import { loadRouteConfig, registerRoutes } from 'adapt-authoring-server'
async init () {
const [auth, server] = await this.app.waitForModule('auth', 'server')
const config = await loadRouteConfig(this.rootDir, this)
const router = server.api.createChildRouter(config.root)
registerRoutes(router, config.routes, auth)
}| Property | Required | Description |
|---|---|---|
route |
Yes | Express-style route path (e.g. "/reset/:id") |
handlers |
Yes | Object mapping HTTP methods to handler method names on the module |
permissions |
No | Object mapping HTTP methods to scope arrays (secured) or null (unsecured) |
meta |
No | Object mapping HTTP methods to OpenAPI operation metadata |
internal |
No | When true, restricts the route to localhost |
override |
No | When true and defaults are in use, merges this route onto the matching default route instead of adding a duplicate |
Handler strings are resolved to bound methods on the module instance automatically.
If you're building a non-trivial API (particularly one that uses the database), we highly recommend that you use AbstractApiModule as a base, as this includes a lot of boilerplate code and helper functions to make handling HTTP requests much easier. See this page for more info on using the AbstractApiModule class.
This may go without saying, but please stick to standardised HTTP response codes; they state your intentions and make it nice and easy for other devs to work with and react to.
See the Mozilla Developer Network docs for a full list of HTTP response status codes and what they mean.
Every /api response is sent with Cache-Control: private, no-cache by default. API responses vary by the caller's authentication, but a shared cache (a CDN or proxy) keys on the URL and ignores the session cookie — so without this a shared cache could store one user's response and serve it to another. private keeps shared caches out while still allowing the caller's own browser cache and ETag revalidation; no-cache makes the browser revalidate rather than serve a stale copy.
If an endpoint is genuinely public and user-agnostic (e.g. a static reference resource), a handler may override this by setting its own Cache-Control before responding:
async myPublicHandler (req, res) {
res.set('Cache-Control', 'public, max-age=3600')
res.json(data)
}The Server module adds a sendError utility function to the ServerResponse object that's passed to every route handler in the stack. Making use of this in your own code will ensure errors are formatted in a consistent way.
Using the helper function is as simple as:
async myHandler(req, res, next) {
try {
// do some stuff
} catch (e) {
res.sendError(e);
}
}See the Express.js documentation for information on the extra functions available: