Skip to main content

Linter

Refract uses ESLint with the flat config format and Prettier for formatting. Every package has its own config, but they all follow the same conventions. Here’s everything you need to know to stay in sync with the linter — and to make Cursor catch problems before you even save a file.

ESLint configuration

Refract uses ESLint’s flat config format, which means each package has an eslint.config.mjs (or .js) file at its root instead of the older .eslintrc format. Flat config gives you explicit, composable control over which rules apply where.

Backend (apps/backend/eslint.config.mjs)

The backend config extends typescript-eslint’s recommended rules, integrates Prettier (via eslint-config-prettier/flat to turn off any rules that conflict with formatting), and adds import ordering enforcement:
The import/order rule enforces alphabetical, grouped imports — so Node built-ins come first, then external packages, then your own files. This keeps diffs clean and makes it obvious at a glance what a file depends on.

Frontend (apps/portal/eslint.config.js)

The frontend adds React-specific rules on top of the TypeScript base:
The react-hooks plugin catches things like missing dependencies in useEffect — the kind of bug that causes subtle stale-closure issues that are painful to debug in production.

Tool packages and shared

Tool packages (apps/tools/*/*) and apps/shared use the same minimal config: typescript-eslint recommended + Prettier + project-aware type checking:

Active rules reference

Here’s what’s actually enforced across the codebase, broken down by package.

Backend-only rules (apps/backend)

Frontend-only rules (apps/portal)


Prettier configuration

Every package with a .prettierrc uses the same settings:
A few things worth knowing:
  • arrowParens: "avoid" means x => x instead of (x) => x for single-argument arrow functions.
  • trailingComma: "all" adds trailing commas everywhere valid — including function parameters. This makes multi-line diffs cleaner because adding a new argument doesn’t touch the previous line.
  • printWidth: 100 is a soft limit. Prettier will still wrap at shorter lengths when it makes sense.

Running the linter

These commands run inside the Docker container, so you don’t need ESLint installed locally.
💡 Tip: if you’re seeing a lot of import/order errors, run the formatter first — Prettier won’t fix import order, but ESLint can auto-fix it:

Wiring ESLint into Cursor

Getting ESLint inline in Cursor means you see problems as you type, not when CI catches them. Here’s how to set it up:

1. Install the ESLint extension

Open Cursor’s extension panel and install ESLint (by Microsoft, dbaeumer.vscode-eslint). It’s the same extension as VS Code.

2. Point it at the right config

Because Refract is a monorepo, you want ESLint to resolve configs relative to the file you’re editing, not the workspace root. Add this to your Cursor settings.json (Cmd+Shift+P → Preferences: Open User Settings (JSON)):
eslint.workingDirectories tells the extension to run ESLint from each package root when editing files in that package — which is what picks up the correct eslint.config.mjs and tsconfig.json. To have Prettier format automatically when you save:
You’ll need the Prettier - Code formatter extension (esbenp.prettier-vscode) installed as well.
⚠️ Watch out: ESLint runs on your host machine here, which means it needs access to the project’s node_modules. Run make deps-install first to ensure the workspace deps are installed in the host-accessible node_modules/ folders.

What’s next?