Skip to content

Latest commit

 

History

History
103 lines (83 loc) · 4.33 KB

File metadata and controls

103 lines (83 loc) · 4.33 KB

Handling server requests

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.

Defining routes with routes.json

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)
}

Route properties

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.

Extend the AbstractApiModule class

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.

Use HTTP status codes

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.

Response caching

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)
}

Use the built-in error handler

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: