A CASA app is a single, isolated ExpressJS "app" instance.
You can create multiple CASA app instances and bring them all together under a "parent" app.
These can each run in isolation, you can combine them all into a larger service, or a mixture of both approaches.
THe methods used to set these up are all geared around the concept of keeping apps as de-coupled from each other as possible, to make them more portable and less brittle when making changes.
- Supported configurations
- Isolated sub-apps
- Sequenced sub-apps
- Sub-apps with different cookie settings
- Tips on reusing session stores
If we think of sub-apps in terms of their Plans, CASA supports the following configurations:
- Isolated Plans:
PlanA
,PlanB
,PlanC
- Sequenced Plans, with a single entrypoint:
PlanA -> PlanB -> PlanC
- A mix of isolated and sequenced Plans:
PlanA
,PlanB -> PlanC
,PlanB -> PlanD
In terms of sequenced Plans, a Plan can only have one "parent" Plan, so the following setups are not supported:
- Multiple parents:
PlanA -> PlanB
,PlanC -> PlanB
By default, all Plans are considered "isolated"; a user can access any of these Plans directly if they know its URL, so they could skip ahead in a sequence of Plans, for example.
However, you can disable this behaviour by defining an entrypoint condition for each app (which is just a bit of middleware). See further below for an example.
In this example, each CASA app is entirely independent. They do not share a session store.
- Each app may use the same session store (waypoints must be unique among all Plans if so)
import { configure } from "@dwp/govuk-casa";
function createApp() {
const { mount } = configure();
return mount(express());
}
const app1 = createApp();
const app2 = createApp();
const parent = express();
parent.use("/one/", app1);
parent.use("/two/", app2);
app.listen();
In this example, we combine multiple aub-apps to effectively form one larger, linear Plan.
- Apps must share the same session store (and all waypoints must be unique)
- Entrypoint conditions must be defined for each app except the one you want to act as the starting point
import ExpressJS from "express";
import { configure } from "@dwp/govuk-casa";
const { MemoryStore } = express;
function createApp(store, entrypointCondition) {
const { mount, ancillaryRouter } = configure({
session: {
store,
name: "session-name",
},
});
const app = ExpressJS();
ancillaryRouter.use(entrypointCondition);
return mount(app);
}
const store = new MemoryStore();
const app1 = createApp(store);
const app2 = createApp(store, (req, res, next) => {
if (someCondition === true) {
next(); // let user through
} else {
res.redirect(302, "/one/"); // send user back to main app
}
});
const parent = ExpressJS();
parent.use("/one/", app1);
parent.use("/two/", app2);
app.listen();
To get the most out of this setup, you will want to link the Plans via some url://
waypoint references.
See the multiapp example app to see this in action.
You may wish to run a sub-app that uses an entirely different cookie to hold information about its session. For example, its name or path is different between sub-apps.
In these cases, you must use a different instance of the session store
. Note that each store
instance can connect to same backing persistence store, using the same credentials for example, but the instances in code must be separate.
For example:
import ExpressJS from "express";
import { configure } from "@dwp/govuk-casa";
function createApp(sessionName) {
const { mount } = configure({
session: {
store: createNewStoreInstance(), // e.g. might create a new RedisStore instance
name: sessionName,
},
});
const app = ExpressJS();
return mount(app);
}
const app1 = createApp("app-1");
const app2 = createApp("app-2");
const parent = ExpressJS();
parent.use("/one/", app1);
parent.use("/two/", app2);
app.listen();
If you reuse the same session store
instance between sub-apps, be aware that it will have various event listeners attached to it for each sub-app. Despite incurring a small resource overhead, this is not functionally deterimental.
In some scenarios with many sub-apps (more than 10), this can lead to a warning similar to this however:
MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
You can choose to disable this warning by setting a new limit on your store
, for example:
const store = new MemoryStore();
store.setMaxListeners(20);