Local Setup

Run the erxes backend and main frontend from source, with MongoDB and Redis running locally.

Run commands from the repository root unless a step says otherwise. The commands below use a Unix-compatible shell on macOS or Linux; on Windows, use WSL2.

Prerequisites

  • Git to clone the repository.
  • Node.js 22, matching the backend CI runtime. A version manager such as nvm is optional.
  • pnpm 9.12.3, the version declared in the root package.json packageManager field.
  • Docker with Compose for the database example below, or locally installed MongoDB and Redis services.
  • curl and internet access for the gateway's first-run Apollo Router download.

Nx is a repository dependency. Use pnpm nx after installing dependencies; a global Nx installation is unnecessary.

If you use nvm and Corepack, select the toolchain with:

nvm install 22
nvm use 22
corepack enable
corepack prepare [email protected] --activate

Check the active versions:

node --version
pnpm --version

Clone the repository

git clone https://github.com/erxes/erxes.git
cd erxes

To work from a release instead of the development branch, check out a tag (git tag -l lists them). A tag checkout puts Git in detached-HEAD mode; create a branch before making changes, and see the contribution workflow when targeting an upstream contribution.

If you already have a checkout, open a terminal at its root instead. The root contains package.json, pnpm-workspace.yaml, backend/, and frontend/.

Start MongoDB and Redis

The following development example uses MongoDB 7 as a single-node replica set and Redis 7. A replica set supports the multi-document transactions plugins run through startSession() calls, so use one even for core-only work. The Compose file is a development example, not a product installer; the databases run in Docker while erxes runs on your host machine.

  1. Create the Compose file

    Create docker-compose.local.yml in the repository root:

    services:
      mongo:
        image: mongo:7
        command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
        ports:
          - "127.0.0.1:27017:27017"
        volumes:
          - erxes-mongo:/data/db
        healthcheck:
          test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand({ ping: 1 })"]
          interval: 5s
          timeout: 5s
          retries: 20
    
      redis:
        image: redis:7
        ports:
          - "127.0.0.1:6379:6379"
        volumes:
          - erxes-redis:/data
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 5s
          retries: 20
    
    volumes:
      erxes-mongo:
      erxes-redis:
    
  2. Start the containers

    docker compose -f docker-compose.local.yml up -d --wait
    

    Initialize the replica set once, when using a new MongoDB volume:

    docker compose -f docker-compose.local.yml exec mongo mongosh --quiet --eval 'rs.initiate({_id: "rs0", members: [{_id: 0, host: "localhost:27017"}]})'
    
  3. Check database health

    docker compose -f docker-compose.local.yml exec mongo mongosh --quiet --eval 'db.hello().isWritablePrimary'
    docker compose -f docker-compose.local.yml exec redis redis-cli ping
    

    The expected responses are true and PONG. Election may take a few seconds; repeat the MongoDB check if it returns false. On later starts, reuse the volumes and skip rs.initiate().

Using existing local databases

You can instead install MongoDB and Redis directly. Start both services and configure MongoDB as a replica set for the setup above. Adjust the connection settings below to match your local ports, replica-set name, and authentication settings.

Configure the environment

Copy the repository's sample file if you do not already have a local .env:

cp .env.sample .env

Most sample entries are commented out, and several variables below are not in the sample. Open .env in your editor and add or uncomment these values for the Docker setup above:

NODE_ENV=development
VERSION=os
MONGO_URL=mongodb://127.0.0.1:27017/erxes?replicaSet=rs0&directConnection=true
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

DOMAIN=http://localhost:3001
REACT_APP_API_URL=http://localhost:4000
GATEWAY_URL=http://localhost:4000

ENABLED_PLUGINS=sales
  • REDIS_HOST and REDIS_PORT are read by the shared Redis client. REDIS_URL does not configure that client. Set REDIS_PASSWORD too if your Redis server requires authentication.
  • DOMAIN is the frontend origin; REACT_APP_API_URL points the frontend to the gateway. Keep these origins consistent with the URLs you open in the browser.
  • GATEWAY_URL supplies the directly reachable gateway address for generated email links; unset, Core API falls back to ${DOMAIN}/gateway.
  • ENABLED_PLUGINS=sales starts the existing sales_api and sales_ui projects. Use base plugin names, not the _api or _ui suffixes. The startup scripts append the suffix themselves, so ENABLED_PLUGINS=sales_api looks for a nonexistent sales_api_api project.
  • Multiple plugins use a comma-separated list without spaces, for example ENABLED_PLUGINS=sales,operation. Only list plugins present in your checkout.
  • For core-only development, omit ENABLED_PLUGINS or leave it empty. The startup scripts handle both cases. Do not set it to the literal text null or a placeholder name.

Optional background services can be enabled with:

ENABLED_SERVICES=automations,logs

The API startup script maps these names to automations-service and logs-service. Enable them when working with automation execution and persisted logs; the logs service also persists the automatic undo journal. ENABLED_PLUGINS_ONLY_API can enable additional backend-only plugins without adding frontend remotes.

Set a private JWT_TOKEN_SECRET for local authentication instead of relying on the source-code fallback ('SECRET'). Generate a value with openssl rand -hex 32 and add it to your local .env. Keep that value consistent across backend services and out of source control.

Restart the development processes after changing plugin or environment settings.

Install dependencies and build the shared API library

pnpm install
pnpm nx build erxes-api-shared

Backend services use the compiled entry points of erxes-api-shared. Build it before starting them, and rebuild it after changing that library's source.

The root pnpm workspace includes backend/** and frontend/**. Standalone applications under apps/, such as the client portal and help center, have separate setup requirements and are not started by the commands below.

Start erxes

Use two terminals, both at the repository root.

  1. Start the backend

    pnpm dev:apis
    

    The script (start-api-dev.js) starts Core API, the gateway, enabled plugin APIs, and any enabled background services. With the example configuration, it includes sales_api on port 3305.

    On first startup, the gateway downloads Apollo Router v1.59.2 into backend/gateway/src/apollo-router/temp/ and composes the federated GraphQL schema from the registered services. Wait for the APIs and router to finish starting before opening the UI. The router listens on internal port 50000 by default (APOLLO_ROUTER_PORT overrides it); clients connect through the gateway on 4000.

  2. Start the frontend

    pnpm dev:uis
    

    The script (start-ui-dev.js) starts the Core UI host on port 3001 and the frontend remotes listed in ENABLED_PLUGINS. With sales enabled, its remote runs on port 3005. Use the Core UI host as the application entry point.

Check it works

  1. Check the backend health endpoints:
curl --fail http://localhost:3300/health
curl --fail http://localhost:4000/health

Both should return ok. A gateway health response confirms the HTTP server is running; it does not by itself confirm that GraphQL composition has finished.

  1. Check the federated endpoint separately:
curl --fail http://localhost:4000/graphql \
  -H 'Content-Type: application/json' \
  --data '{"query":"query LocalSetupHealth { __typename }"}'

Once the router is ready, the response should contain data with __typename set to Query. HTTP 200 with a GraphQL errors array is not a successful API check.

  1. Open http://localhost:3001. On a fresh installation without an owner, the frontend calls /initial-setup on the gateway, sees hasOwner: false, and directs you to /create-owner. Complete that form to create your initial account. If an owner already exists, sign in with the account for that local database.

Local service URLs

ServiceURLPurpose
Core UIlocalhost:3001Main application
Gateway healthlocalhost:4000/healthGateway HTTP health check
GraphQLlocalhost:4000/graphqlFederated API endpoint
Core API healthlocalhost:3300/healthCore API health check
Sales APIhttp://localhost:3305Enabled by the example plugin configuration
Sales UI remotehttp://localhost:3005Loaded by the Core UI host

Troubleshooting

MongoDB or Redis connection refused

Check that the containers are running and healthy:

docker compose -f docker-compose.local.yml ps
docker compose -f docker-compose.local.yml logs mongo redis

Match the published ports with MONGO_URL, REDIS_HOST, and REDIS_PORT. For this host-based setup, use 127.0.0.1, not Docker service names such as mongo or redis.

MongoDB reports no primary or replica-set errors

Initialize the replica set on a new volume, then confirm db.hello().isWritablePrimary returns true. The URI's replicaSet value must match the configured set name (rs0 in this guide). An AlreadyInitialized response means the volume already has a replica-set configuration; inspect it with rs.conf() instead of initializing it again.

If a plugin fails with Transaction numbers are only allowed on a replica set member or mongos, your mongod is running standalone; convert it to a replica set.

Apollo Router download fails on first startup

The gateway fetches the router binary with curl -sSL https://router.apollo.dev/download/nix/v1.59.2 | sh into backend/gateway/src/apollo-router/temp/ whenever that file is missing and NODE_ENV is not production. A network restriction or interrupted download leaves the gateway stuck at startup. Check outbound access to router.apollo.dev, delete a partial temp/ directory, and restart pnpm dev:apis. You can also run the same curl command inside backend/gateway/src/apollo-router/temp/ manually, as the gateway's error message suggests.

A plugin project or remote cannot be found

ENABLED_PLUGINS takes base names: sales maps to sales_api and sales_ui. Writing sales_api produces sales_api_api, which does not exist. Check spelling, remove spaces and trailing commas, and confirm both matching projects exist. posclient has a backend plugin but no posclient_ui, so it belongs in ENABLED_PLUGINS_ONLY_API, not ENABLED_PLUGINS. Restart both development terminals after changing the list.

Cannot resolve erxes-api-shared

Run pnpm nx build erxes-api-shared from the repository root, then restart the affected API. Repeat the build after editing the shared library.

GraphQL is unavailable but gateway health is OK

Inspect the backend terminal for service registration, schema composition, or Apollo Router errors. Confirm the selected APIs are running and that port 50000 is available. The first run requires access to the Apollo Router download service.

A port is already in use

Stop the other process using the reported port. The default setup needs ports 27017, 6379, 3300, 4000, 50000, and 3001, plus each enabled plugin's API and UI ports. Avoid running a second copy of Core API or the gateway alongside pnpm dev:apis.

Node.js runs out of heap memory

Start with fewer plugins. If your machine has enough available memory, add NODE_OPTIONS=--max-old-space-size=4096 to .env and restart the development processes. This limit applies per Node.js process, so account for the number of services you run concurrently.

Stop and restart

Press Ctrl+C in each development terminal. Stop the database containers with:

docker compose -f docker-compose.local.yml down

The named volumes retain your local data. To resume, start the containers with up -d --wait, then run pnpm dev:apis and pnpm dev:uis again. Reinitializing the replica set is unnecessary when reusing the same MongoDB volume.

Was this helpful?