> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/development/dev-environment.md).

# Dev Environment

MyIP is one repository with two halves: a **Vue 3** single-page app under `frontend/` and an **Express 5** API under `api/` + `backend-server.js`. One command runs both.

## Prerequisites

<table><thead><tr><th width="180">Tool</th><th width="220">Version</th><th>Notes</th></tr></thead><tbody><tr><td><strong>Node.js</strong></td><td>24</td><td>What the Docker image (<code>node:24-alpine</code>) and CI both use.</td></tr><tr><td><strong>pnpm</strong></td><td>Pinned in <code>package.json</code></td><td>Do not install a different version by hand — see below.</td></tr><tr><td><strong>Git</strong></td><td>Any recent version</td><td>Contributions branch off <code>dev</code>.</td></tr></tbody></table>

The easiest way to get the right pnpm is Corepack, which ships with Node:

```bash
corepack enable
```

Corepack reads the `packageManager` field in `package.json` and provisions exactly that pnpm version. The Dockerfile and the CI workflow do the same, so your local toolchain matches theirs.

## pnpm only

{% hint style="danger" %}
**Never run `npm install` or `yarn` in this repo.**
{% endhint %}

Three things depend on pnpm specifically:

* **`packageManager` in `package.json` pins the exact pnpm version.** Corepack, the Dockerfile, and the GitHub Actions workflow all read it, so everyone resolves the same dependency tree.
* **`pnpm-lock.yaml` is committed.** npm would write `package-lock.json` and yarn would write `yarn.lock` — a second, competing lockfile that nothing in the project reads. CI installs with `--frozen-lockfile`, so a drifted pnpm lockfile fails the build outright.
* **`pnpm-workspace.yaml` carries the install-script approvals** (`allowBuilds`) for the few packages allowed to run postinstall scripts. npm and yarn ignore that file entirely.

## Install and run

{% stepper %}
{% step %}

#### Clone and install

```bash
git clone https://github.com/jason5ng32/MyIP.git
cd MyIP
pnpm install
```

{% endstep %}

{% step %}

#### Create your `.env`

```bash
cp .env.example .env
```

Everything in `.env.example` is optional to *start* the app, but IP geolocation stays broken without MaxMind credentials. See [MaxMind Setup](/developer/getting-started/maxmind-setup.md) and [Environment Variables](/developer/reference/environment-variables.md).
{% endstep %}

{% step %}

#### Start both halves

```bash
pnpm dev
```

This runs the Vite dev server and the backend (under `nodemon`) side by side via `concurrently`. Edit a `.vue` file and Vite hot-reloads it; edit anything the backend imports and nodemon restarts the API.
{% endstep %}

{% step %}

#### Open the app

Go to `http://localhost:18966`. The Vite dev server proxies `/api` to the backend for you.
{% endstep %}
{% endstepper %}

## Ports

Both ports come from `.env` and default as follows:

| Variable        | Default | Used by                                                           |
| --------------- | ------- | ----------------------------------------------------------------- |
| `FRONTEND_PORT` | `18966` | Vite dev server (`pnpm dev`) and the static server (`pnpm start`) |
| `BACKEND_PORT`  | `11966` | Express API (`backend-server.js`)                                 |

The Vite dev server binds `0.0.0.0`, so you can reach it from another device on your LAN, and proxies `/api` → `http://localhost:<BACKEND_PORT>`. You rarely need to hit the backend port directly.

{% hint style="warning" %}
**Requests to `/api/*` need a `Referer`.** A global middleware (`requireReferer` in `common/guards.js`) rejects requests whose referer is not on the allow-list. `localhost` is allowed, so the browser is fine — but a bare `curl http://localhost:11966/api/...` gets a `403`. Add `-H 'Referer: http://localhost/'` when testing by hand.
{% endhint %}

## Local `.env` notes

* `.env` is loaded by **both** halves: `backend-server.js` at runtime and `vite.config.js` at build time.
* Variables prefixed `VITE_` are **baked into the frontend bundle at build time**. Changing one needs a `pnpm dev` restart, not just a page refresh — and never put a secret behind a `VITE_` name.
* Optional integrations stay entirely off when their variable is empty. No Sentry DSN means no Sentry code loads at all; no Firebase config means the SDK is never fetched. You can develop most of the app with an almost-empty `.env`.
* Logging knobs are `LOG_LEVEL`, `LOG_FORMAT`, and `LOG_HTTP` — see [Logging](/developer/configuration/logging.md). There is no `NODE_ENV` switch anywhere in this project.

## Every script

| Command               | What it does                                                   |
| --------------------- | -------------------------------------------------------------- |
| `pnpm dev`            | Vite dev server + backend under nodemon, together              |
| `pnpm build`          | Production frontend build into `dist/`                         |
| `pnpm preview`        | Vite's preview server for the built output                     |
| `pnpm test`           | `node --test tests/*.test.js`                                  |
| `pnpm check`          | `test` + `build` — the pre-commit self-check                   |
| `pnpm start`          | Static frontend server + backend (what a production host runs) |
| `pnpm start-backend`  | Backend only                                                   |
| `pnpm start-frontend` | Static frontend server only                                    |

## The self-check

Before you hand off a change — a PR, a commit, a review request — run:

```bash
pnpm check
```

That is `pnpm test` followed by `pnpm build`. It is the same pair of steps CI runs on every push and pull request against `main` and `dev`, so a green local `check` usually means a green CI run.

{% hint style="info" %}
**Visual changes can't be self-tested.** The Node test runner does not render Vue components or drive a browser. If your change is visual, say so explicitly in the PR and let a human look at it in `pnpm dev`. See [Testing](/developer/development/testing.md).
{% endhint %}

## Handy extras in dev

* **Mobile console.** On a phone or tablet, `pnpm dev` loads vConsole automatically — an on-screen devtools panel. It is dev-only and mobile-only; it never ships in a build.
* **Click-to-source.** The `code-inspector-plugin` is wired into the dev server, so you can jump from an element in the browser to its source line in your editor.
* **Extra dev hosts.** `vite.config.js` allows `dev.ipcheck.ing` and `test.ipcheck.ing` in addition to localhost, for testing against a real hostname.

## Next

* [Coding Conventions](/developer/development/coding-conventions.md) — the rules your change is expected to follow.
* [Adding a New Tool](/developer/development/adding-a-new-tool.md) — the end-to-end walkthrough.
* [Project Structure](/developer/architecture/project-structure.md) — what lives where.
* [How to Contribute](/developer/contributing/how-to-contribute.md) — branch discipline and PR guidelines.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/development/dev-environment.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
