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.jsonpackageManagerfield. - Docker with Compose for the database example below, or locally installed MongoDB and Redis services.
curland 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.
Create the Compose file
Create
docker-compose.local.ymlin 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:Start the containers
docker compose -f docker-compose.local.yml up -d --waitInitialize 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"}]})'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 pingThe expected responses are
trueandPONG. Election may take a few seconds; repeat the MongoDB check if it returnsfalse. On later starts, reuse the volumes and skiprs.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_HOSTandREDIS_PORTare read by the shared Redis client.REDIS_URLdoes not configure that client. SetREDIS_PASSWORDtoo if your Redis server requires authentication.DOMAINis the frontend origin;REACT_APP_API_URLpoints the frontend to the gateway. Keep these origins consistent with the URLs you open in the browser.GATEWAY_URLsupplies the directly reachable gateway address for generated email links; unset, Core API falls back to${DOMAIN}/gateway.ENABLED_PLUGINS=salesstarts the existingsales_apiandsales_uiprojects. Use base plugin names, not the_apior_uisuffixes. The startup scripts append the suffix themselves, soENABLED_PLUGINS=sales_apilooks for a nonexistentsales_api_apiproject.- 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_PLUGINSor leave it empty. The startup scripts handle both cases. Do not set it to the literal textnullor 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.
Start the backend
pnpm dev:apisThe 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_apion port3305.On first startup, the gateway downloads Apollo Router
v1.59.2intobackend/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 port50000by default (APOLLO_ROUTER_PORToverrides it); clients connect through the gateway on4000.Start the frontend
pnpm dev:uisThe script (start-ui-dev.js) starts the Core UI host on port
3001and the frontend remotes listed inENABLED_PLUGINS. Withsalesenabled, its remote runs on port3005. Use the Core UI host as the application entry point.
Check it works
- 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.
- 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.
- Open http://localhost:3001. On a fresh installation without an owner, the frontend calls
/initial-setupon the gateway, seeshasOwner: 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
| Service | URL | Purpose |
|---|---|---|
| Core UI | localhost:3001 | Main application |
| Gateway health | localhost:4000/health | Gateway HTTP health check |
| GraphQL | localhost:4000/graphql | Federated API endpoint |
| Core API health | localhost:3300/health | Core API health check |
| Sales API | http://localhost:3305 | Enabled by the example plugin configuration |
| Sales UI remote | http://localhost:3005 | Loaded 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.