Getting started
Install Neatship, start the database, the API server, the web app and the background worker on your computer, and open the app as a sample user.
By the end of this guide, the whole app runs on your computer: the web app, the API server, the background worker and a database filled with sample data. You don't need a Clerk account or a Stripe account for this. You can add them later.
Tip: Prefer to be guided? Open Claude Code in the repository folder and type /start-here. It explains the project, runs these steps with you and checks each one. With another assistant, ask it to follow .claude/skills/start-here/SKILL.md.
What you get
Neatship is one repository that holds several packages (a package is a folder with its own code and its own settings):
| Package | What it is |
|---|---|
packages/app-front | The web app your customers use (React) |
packages/app-server | The API server (NestJS, GraphQL, PostgreSQL) and its background worker |
packages/app-shared | Types and helpers used by both the web app and the server |
packages/app-ui | The design system: colors, fonts, spacing, components, icons |
packages/app-website | A landing page and these docs, prerendered for search engines |
packages/app-lint-rules | 17 custom code checks written for this starter |
packages/app-e2e-testing | End-to-end tests: a browser clicks through the real app |
packages/app-docker | Docker files: local database and Redis, and the production stack |
packages/app-utils | Setup, check, production and backup scripts |
The app already has sign-in, workspaces (one per customer organization), settings pages, a billing page, English and French, and one complete example feature called "Projects". You copy that example to build your own features. Everything production needs is there too (rate limits, logs, alerts, nightly backups, HTTPS): when you are ready, Deploy puts it on a server and Security ends with the checklist to follow before your first real customer.
The repository also holds a rulebook for AI assistants: CLAUDE.md (also readable as AGENTS.md) and docs/conventions/. See Working with AI assistants.
Requirements
- Node.js 24.13.0. The exact version is in
.nvmrc. Node.js runs the server and all the tools. If you use nvm (a tool that switches Node versions), runnvm installthennvm usein the repository folder. The install stops with a clear error if your Node version doesn't match^24.5.0. - Corepack. It ships with Node.js. It gives you the exact version of Yarn (the package manager, the tool that downloads the libraries) that the repository asks for: Yarn 4.18.0.
- Docker Desktop, or Postgres and Redis installed on your machine. Postgres is the database. Redis is a fast in-memory store; here it holds the queue of background jobs and the rate limit counts.
The setup script prefers Docker. If Docker doesn't answer within 10 seconds, it looks for a local Postgres and Redis instead (it needs the pg_ctl, initdb and redis-server commands on your PATH). If neither is available, it stops and says so.
Tip: To force the local services even when Docker is installed, run bash packages/app-utils/setup-dev-env.sh --native. Their data then lives in .cache/dev-services at the root of the repository. Once that folder holds the data, the script keeps using it, even when Docker is back.
Make it your own repository
You have access to the starter's private repository. Your product gets a repository of its own, which keeps a link to the starter so you can bring in its updates later. Do this once per product.
- On GitHub, create an empty private repository for your product, for example
my-app. Leave it empty: no README, no license, no.gitignore. - Copy the starter into a folder named after your product, and connect that folder to your repository:
git clone <the starter's address> my-app # the address is under the green "Code" button of the starter's page
cd my-app
git remote rename origin starter # "starter" now names the starter's repository
git remote add origin <your repository's address>
git push -u origin mainFrom now on, git push sends your work to your repository. The starter's repository is only read, when you bring in an update (see "Bring in the starter's updates" below).
Warning: Don't use GitHub's "Fork" button: a fork of a private repository depends on your access to it, and its history can't be separated later. Don't copy the files either: without the shared history, every update would have to be compared file by file.
Install
Run these commands at the root of the repository:
corepack enable
yarn installThe install is hardened on purpose (.yarnrc.yml):
- installed packages can't run their own install scripts, the most common way npm malware spreads;
- Yarn refuses any package version published less than 3 days ago;
- Yarn checks the lockfile (
yarn.lock, the list of exact versions) against the registry.
None of this needs any action from you. It only explains why yarn add sometimes picks a version that is a few days old.
Prepare the database: yarn setup
yarn setupThis runs packages/app-utils/setup-dev-env.sh. It does five things:
- It starts Postgres on port 5433 and Redis on port 6380, in Docker or directly on your machine.
- It prepares two databases:
defaultfor development andtestfor the automated tests. - It creates the
.envfiles (the files that hold your local settings and keys) inpackages/app-server,packages/app-frontandpackages/app-website, by copying each.env.example. An existing.envis never overwritten. - It applies the database migrations (the scripts that create and change tables) to both databases.
- It adds the sample data to the
defaultdatabase.
You can run it again at any time. It only does what is missing.
Start the app: yarn start
yarn startThis starts three processes at once:
- the API server, which restarts when you save a server file;
- the web app, which reloads in the browser when you save a front-end file;
- the background worker, which runs queued jobs such as Stripe and Clerk webhooks.
Open the web app at http://localhost:5180.
| Service | Address |
|---|---|
| Web app | http://localhost:5180 |
| API server | http://localhost:3100 |
| GraphQL API | http://localhost:3100/graphql (open it in a browser in development to get GraphiQL, an interactive query editor) |
| Health check | http://localhost:3100/healthz |
| Postgres | localhost:5433, user app, databases default and test |
| Redis | localhost:6380 |
Two more things run on demand, not with yarn start:
npx nx start app-website # the landing page, on http://localhost:5190
npx nx run app-ui:storybook:serve:dev # the design system catalog, on http://localhost:6008The sample user and the two optional services
The app runs right away because sign-in and billing each have a stand-in mode:
| Feature | Without an account (default) | With an account |
|---|---|---|
| Sign-in | AUTH_PROVIDER=mock: every request is signed in as Ada Lovelace, admin of the "Acme team" workspace | AUTH_PROVIDER=clerk: real accounts, workspaces and invitations. See Authentication. |
| Billing | BILLING_PROVIDER=none: the Billing page shows the plans and the upgrade buttons say "Soon" | BILLING_PROVIDER=stripe: Stripe Checkout and Stripe's customer portal. See Billing. |
The sample data (from npx nx run app-server:database:seed) has two workspaces:
| Workspace | Person | Role | |
|---|---|---|---|
| Acme team | Ada Lovelace | ada@example.com | admin |
| Acme team | Grace Hopper | grace@example.com | admin |
| Acme team | Alan Turing | alan@example.com | member |
| Globex | Bob Smith | bob@example.com | admin |
Acme team has 3 projects (one of them archived). Globex has 1. Globex exists to prove that one workspace never sees the data of another.
In mock mode, tools and tests can act as another seeded person with the x-mock-user-email header. The value anonymous simulates a signed-out request. For example, to list projects as Alan (a member):
curl -s http://localhost:3100/graphql \
-H 'content-type: application/json' \
-H 'x-mock-user-email: alan@example.com' \
-d '{"query":"{ projects { name status } }"}'Warning: The mock signs people in without any password. The server refuses to start with AUTH_PROVIDER=mock when NODE_ENV=production.
Everyday commands
Run them at the root of the repository.
yarn start # server, web app and worker
yarn verify # the definition of done: checks what changed
npx nx run-many -t lint typecheck test # every check, every package
npx nx lint app-front --configuration=fix # fix lint and formatting in one package
npx nx test app-server # unit tests of one package
npx nx run app-server:test:integration # API tests against the test database
npx nx run app-server:database:migrate # apply pending migrations
npx nx run app-server:database:seed # add the sample data again (safe to rerun)
npx nx run app-front:lingui:extract # after adding or changing on-screen text
npx nx run app-front:lingui:compile
yarn check:forbidden-words # the word check that CI also runs
bash packages/app-utils/setup-dev-env.sh --down # stop Postgres and Redis
bash packages/app-utils/setup-dev-env.sh --reset # delete the local data and start freshnpx nx runs Nx, the tool that runs tasks for each package. It only reruns what changed since the last run.
Several products on one computer
Each copy of the starter keeps its own database and job queue. In Docker they are named after the folder: my-app gets the Docker project my-app-dev. Without Docker, they live in the copy's own .cache/dev-services folder.
Every copy uses the same ports (3100, 5180, 5433 and 6380), so only one runs at a time. To switch from one product to another:
# in the product you leave: press Ctrl+C where yarn start runs, then
bash packages/app-utils/setup-dev-env.sh --down
# in the product you open:
yarn setup
yarn startIf you forget, yarn setup and yarn start stop with a message that names the program using the port and its folder.
Bring in the starter's updates
Updates are published in the starter's repository. You bring them into your product when you choose, on a branch, like any other change:
git switch main && git pull
git fetch starter
git log --oneline main..starter/main # the starter's changes you don't have yet
git switch -c update-from-starter
git merge starter/main- If git reports conflicts (files that both you and the starter changed, often the website's copy,
README.mdorCLAUDE.md), ask your AI assistant: "Resolve the merge conflicts with the starter's update, keeping my product's changes." - Then run
yarn install,yarn setup(it applies the new migrations) andyarn verify:full. - Push the branch, open a pull request, and merge it once CI passes.
When something goes wrong
- "Start Docker Desktop, or install Postgres and Redis locally": the setup script found neither Docker nor a local Postgres and Redis. Start Docker Desktop, then run
yarn setupagain. - "Node version … doesn't match the required version": switch to the version in
.nvmrc. - "Port … is already used by …": another copy of the starter, or an earlier
yarn start, is still running. The message names the program and its folder. See "Several products on one computer". - Type errors in a package you didn't touch, after switching branches: rebuild the shared package with
npx nx build app-shared --skip-nx-cache. Its build output is not stored in git. - You changed a key in a
.envfile and nothing happened: stopyarn startand start it again. The server and the web app read.envfiles at start-up only.
Where to go next
- Project structure: what lives where and how the checks run.
- Working with AI assistants: how to get clean code from Claude Code, Codex, Cursor or others.
- Authentication: turn on real sign-in with Clerk.
- Billing: take test payments with Stripe.
- Add a feature: build your first feature by copying Projects.
- Deploy and Security: put it in production, then work through the checklist before your first real customer.