@sinequa/atomic

Several apps, one backend

Two Sinequa applications on the same server — two configurations, one session, and why isolating them is the mistake.

A Sinequa server hosts as many web applications as an administrator declares, each with its own query web services, facets, tabs and sort options. A portal offering an HR search and a technical search is one server and two apps.

Looking for two servers instead?

Two backends is the other case, and its rule is the opposite of this one — see Multiple backends. Here: one server, one session. There: two servers, two sessions kept apart.

The app travels with every request

app is read from the client's configuration on each request: it goes into the body of every mutating call and into the query of api/v1/app. So two clients pointed at the same server with different app values see two different configurations, without either knowing about the other.

clientapp: hr one serverone session clientapp: technical hr configurationits facets, its tabs technical configuration

The session must be shared

Same server means one origin, one cookie, one CSRF token. Give these clients the same storage prefix — or omit it, which is the default — and they share the session: the user signs in once.

Isolating them with distinct prefixes forces two sign-ins against the same server, and in credentials mode that means two password forms. This is exactly the inverse of the multiple-backends rule.

Two applications, one sign-in

Search two applications side by side
src/apps.ts
import { createAtomicClient } from "@sinequa/atomic";
import type { AtomicClient } from "@sinequa/atomic";

const backendUrl = "https://<your-sinequa-server>";

/**
 * Same server, same session: the prefix is deliberately left at its default so
 * both clients read the one stored token.
 */
export const hr: AtomicClient = createAtomicClient({ app: "<your-hr-app>", backendUrl });
export const tech: AtomicClient = createAtomicClient({ app: "<your-tech-app>", backendUrl });

/**
 * Sign in once, then resolve each configuration. Order matters: two concurrent
 * `login()` calls against one server would race for the same session.
 */
export const ready = (async () => {
  await hr.initializeConfig();
  await hr.auth.login();

  // The session is already established; this only reads the other app's config.
  await tech.initializeConfig();
})();

/** Each client searches its own application's query web services. */
export async function searchBoth(text: string) {
  await ready;

  return Promise.all([
    hr.api.query.search({ name: "<your-hr-query>", text, pageSize: 5 }),
    tech.api.query.search({ name: "<your-tech-query>", text, pageSize: 5 }),
  ]);
}

Or one client, reconfigured

Two clients are for showing two applications together. When the user switches between them, one client is enough — configure({ app }) changes it for every subsequent request:

Switching applications on one client
src/switch-app.ts
import { createAtomicClient } from "@sinequa/atomic";
import type { CCApp } from "@sinequa/atomic";

const client = createAtomicClient({
  app: "<your-hr-app>",
  backendUrl: "https://<your-sinequa-server>",
});

await client.initializeConfig();
await client.auth.login();

let configuration: CCApp | undefined = await client.api.app.get();

/**
 * Switching takes effect on the next request. The configuration you were holding
 * belongs to the previous application, so it has to be re-read — its facets, tabs
 * and query names are all different.
 */
export async function switchTo(app: string) {
  client.configure({ app });

  configuration = await client.api.app.get();
  return configuration;
}

export function currentQueryName() {
  return configuration?.defaultQueryName;
}

Pitfalls

What's next

On this page