Extending the REST API
The hub server uses Express v5 as its underlying http server.
The hub server is first and foremost a GraphQL server that exposes the POST /graphql
REST route.
However, you may want to add additional REST routes. e.g. GET /health
or GET /metrics
or maybe even a custom bet endpoint if you don’t want to use GraphQL.
Using configureApp
To access the underlying Express instance, pass a function to the configureApp
option.
const options: ServerOptions = {
configureApp(app: Express) {
// Do anything with `app` here, like add routes or middleware
},
};
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).then(({ 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:
- Request is not authenticated
- A user is making the request (
Authorization: "session:{sessionToken}"
was valid) - 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:
// Maybe we have a GET /metrics route that only the operator can access
app.get("/metrics", (req: CaasRequest, res: Response, next: NextFunction) => {
if (req.identity?.kind !== "operator") {
return res.status(401).send("Unauthorized");
}
res.json({ metrics: "TODO" });
});