Extending the REST API

@moneypot/hub uses Express v5 as its underlying http server.

@moneypot/hub' is first and foremost a GraphQL server that a exposes GET /graphql and POST /graphql REST routes.

However, you may want to add additional REST routes. e.g. GET /health or GET /metrics.

You can access the underlying Express instance with configureApp(app: Express) => void.

Custom routes and middleware inside `configureApp` are run downstream of `@moneypot/hub`' internal middleware.

Example

import { ServerOptions, startAndListen } from "@moneypot/hub";
import {
  Express,
  CaasRequest,
  Response,
  NextFunction,
} from "@moneypot/hub/express";

const options: ServerOptions = {
  configureApp(app: Express) {
    app.use((req: CaasRequest, res: Response, next: NextFunction) => {
      switch (req.identity?.kind) {
        case "user":
          console.log("Logged in as user", req.identity.user.uname);
          break;
        case "operator":
          console.log("Logged in as operator");
          break;
        default:
          console.log("Unauthenticated request");
          break;
      }
      next();
    });

    app.get("/health", (req: CaasRequest, res: Response) => {
      res.json({ status: "healthy" });
    });
  },
  // ...
};

startAndListen(options, ({ port }) => {
  console.log(`Listening on port ${port}`);
});
You don't have to use the Express re-exports from `@moneypot/hub/express`, but doing so ensures you're using the same version of Express components that are bundled with hub.

Accessing current user

You can access the user info from the request object: req.identity.

It represents three states:

  1. Request is not authenticated
  2. A user is making the request (Authorization: "session:{sessionToken}" was valid)
  3. An operator's api key is making the request (Authorization: "apikey:{apiKey}" was valid)

The shape of req.identity is:

export interface CaasRequest extends Request {
  identity?:
    | {
        kind: "user";
        user: DbUser;
        sessionId: string;
      }
    | { kind: "operator"; apiKey: string };
}

So, you could use it like this to restrict route access to logged-in users:

import {
  Express,
  CaasRequest,
  Response,
  NextFunction,
} from "@moneypot/hub/express";
import { DbUser } from "@moneypot/hub/db";
import * as database from "./database";

app.get("/bets", (req: CaasRequest, res: Response, next: NextFunction) => {
  if (req.identity?.kind !== "user") {
    return res.status(401).send("Unauthorized");
  }

  const user: DbUser = req.identity.user;

  const bets = await database.listBetsForUserId(user.id);
  res.json(bets);
});

And here's how you can use it for a route that is limited to operator api keys:

app.get("/metrics", (req: CaasRequest, res: Response, next: NextFunction) => {
  if (req.identity?.kind !== "operator") {
    return res.status(401).send("Unauthorized");
  }

  res.json({ metrics: "TODO" });
});