Skip to content

Running it locally

There's no single "up" command for the whole platform — each service is its own repo with its own stack. The reliable pattern is: run the APIs (and their Mongos) in Docker, run the fronts on the host with npm, and make sure everyone agrees on the IdP's URL. This page is the map; each repo's CLAUDE.md has the exact commands.

Minimum viable stack

To log in and click around, you need three things up: the Identity service, one product API, and one front for that product. Add the other product when you want to exercise the integration.

Ports at a glance

Service URL Notes
Identity (IdP) http://localhost:8082 OIDC + hosted login at root; API at /api/v1.
Xifrasoft API http://localhost:8081 /api/v1.
Figestió API http://localhost:8080 /api/v2.
Xifrasoft front http://localhost:3001
Figestió backoffice http://localhost:3000
Figestió portal http://localhost:3002
Syncthing http://localhost:8384 REST API the figestió API drives.

Two ways to run each service

%%{init: {'theme':'base','themeVariables':{'fontFamily':'Inter, system-ui, sans-serif','fontSize':'14px','lineColor':'#7d8f8e','primaryColor':'#e7efee','primaryTextColor':'#211915','primaryBorderColor':'#5f9b9a','clusterBkg':'#ffffff00','clusterBorder':'#c4d0cf'}}}%%
flowchart LR
  subgraph host["Host (npm run dev)"]
    fronts["Fronts<br/><small>:3000 :3001 :3002</small>"]
  end
  subgraph docker["Docker (compose)"]
    apis["APIs + Mongo<br/><small>:8080 :8081 :8082</small>"]
  end
  fronts -->|"localhost:8082"| apis
  apis -->|"host.docker.internal:8082"| apis

  class fronts h
  class apis d
  classDef h fill:#d9e8e7,stroke:#4f8f8d,color:#15302f;
  classDef d fill:#f3ece6,stroke:#b39a82,color:#2b211b;
  • APIs in Docker. Each API repo ships a docker-compose that brings up the Express service and its Mongo together. This is the default and the closest to production.
  • Fronts on the host. The Next fronts run with npm run dev (Turbopack) on their ports. They reach the APIs and the IdP over plain localhost.

You can run an API on the host too (handy for debugging), but then mind the URL rule below.

The host.docker.internal rule

A browser and a container resolve localhost to different places. The IdP, fronts, and a host-run API all see localhost:8082 as the IdP. A containerised API does not — to it, localhost is its own container. From inside Docker, point at the host with host.docker.internal:

Caller How it should reach the IdP
A front (browser / Next server on host) http://localhost:8082
An API in Docker http://host.docker.internal:8082
An API on the host http://localhost:8082

So a containerised xifrasoft/figestió API typically sets:

IDP_ISSUER=http://localhost:8082            # must equal the token's `iss`
IDP_JWKS_URI=http://host.docker.internal:8082/.well-known/jwks.json
IDP_BASE_URL=http://host.docker.internal:8082

IDP_ISSUER is not a URL you fetch — it's a string you compare

IDP_ISSUER must match the iss claim the IdP stamps (its ISSUER_URL, http://localhost:8082). IDP_JWKS_URI/IDP_BASE_URL are URLs the API actually calls, so those get host.docker.internal. Mixing these up is the most common "valid token, still 401" bug.

First-run order

  1. Identity up first (its Mongo + the service). In dev it generates an ephemeral RS256 key at boot, so restarting it invalidates existing tokens — log in again.
  2. Seed the OIDC clients (below) so the fronts have something to authenticate against.
  3. A product API up, pointed at the IdP per the rule above.
  4. The matching front with npm run dev.
  5. Open the front, sign up, verify your email (with no RESEND_API_KEY, the link is printed to the IdP's stdout — copy it from the logs), and you're in.

Seed the OIDC clients

The IdP only accepts clients it knows. The identity repo provisions the first-party clients from env overrides:

# in ~/Developer/xifrasoft/identity
npm run seed:clients

It reads, per client, a redirect-URI list and (for confidential clients) a secret:

Client *_REDIRECT_URIS Secret?
figestio-backoffice http://localhost:3000/api/auth/callback yes
xifrasoft-front http://localhost:3001/api/auth/callback yes
figestio-portal http://localhost:3002/api/auth/callback yes
figestio-companion http://127.0.0.1:51789/callback no (public, PKCE only)

Each front then needs the matching OIDC_CLIENT_ID / OIDC_CLIENT_SECRET / OIDC_REDIRECT_URI in its own .env. Redirect URIs must match exactly — a trailing slash mismatch is rejected at /oidc/authorize.

Seed an agency (to exercise the accountant flow)

To see the cross-product accountant experience you need a User who is an agency member, an Agency, and at least one grant over a customer workspace. Using the IdP API with a normal access token:

  1. Create the agency: POST /api/v1/agencies { name } (you become its admin).
  2. Grant a customer workspace: POST /api/v1/agencies/:id/grants { workspaceId, scope } with scope: "manage" to also allow folder assignment.
  3. In the backoffice, switch to the agency context (/auth/refresh?agency_id=) and the granted workspace appears as a customer.

For figestió's file flow you'll also want Syncthing running and an EMPRESES root folder seeded (POST /folders/root); the companion app automates the device side.

The four-places rule for API env vars

This has bitten the team twice, so it's a platform convention. Adding an env var to an API means updating four places or it's silently undefined at runtime (no compile error):

  1. environment.d.ts — the TypeScript declaration.
  2. .env — the value (gitignored, so it's not carried by commits, merges, or worktrees — set it per machine).
  3. docker-compose.yml — the environment: passthrough list.
  4. Recreate the container: docker compose up -d --force-recreate <service>, then verify the value inside the container.

Each API's CLAUDE.md has the full procedure and the per-service variable list. See also Identity config, Xifrasoft config, and Figestió config.