Skip to main content

Why it exists

The OpenSource Together API is a closed-source service. Rather than making contributors ask for credentials, the web app ships a local mock of the entire API contract, seeded with realistic data.
That’s the whole setup: no .env, no database, no Docker, no OAuth app.
This is not the browser service-worker flavour of MSW. It’s a real HTTP server: an Express app serving MSW handlers on port 4000, so server components, generateMetadata, and the sitemap hit it too, exactly like the real API.

The files

Everything lives in src/mocks/:

Sessions without OAuth

The mock issues the same cookie name the real API uses (better-auth.session_token), which is exactly what src/middleware.ts checks for. That’s why protected routes work with no OAuth flow at all. You start signed in. To test signed-out states, use the Mock mode button in the bottom-left corner of the page. It toggles a mock_signed_out cookie and reloads.

Try it

Toggle to Signed out, then visit /dashboard/my-projects. You should be redirected to the login page with a redirectTo parameter.

Seeded state

db.mock.ts clones the fixtures at boot and arranges a few things so every flow is reachable:
  • three projects are owned by the mock user, so the dashboard isn’t empty
  • one project has no owner, so claiming can be tested
  • one project is bookmarked, so the bookmarks list isn’t empty
  • uploaded images are kept in memory and served from /mock-uploads/:id
All of this is per-process and in-memory. Restarting pnpm dev:mock resets everything, intentionally, so every contributor and every test run starts from identical state.

Adding a handler

When you build a feature that calls an endpoint the mock doesn’t cover, the request returns 501 and the terminal logs:
That’s your cue. Add the handler in src/mocks/handlers.mock.ts.

Response helpers

Use the helpers. They produce the exact envelopes the real API returns, which is what the apiData() client expects.

An example

Two details that matter:
  • api(path) wraps the path as *${path} so the handler matches regardless of host. Always use it.
  • Check auth first. If the real endpoint is guarded, the mock should be too. Otherwise you’ll build a flow that breaks in production.
Match the real contract, not what’s convenient. Check the API Reference for the actual response shape and status codes before writing the handler. A mock that returns a different shape hides a bug rather than preventing one.

Add seed data if you need it

New fields go in src/mocks/fixtures/. Project data lives in fixtures/projects.mock.json, which is large. Biome deliberately skips formatting it.

Add a test

If you change mock behaviour or state, add a case to src/mocks/handlers.mock.test.ts:
The suite runs the handlers through msw/node with onUnhandledRequest: "error", and resets the database before each test.

Deliberate gaps

Some things aren’t reproduced locally, on purpose: There’s also one route that exists only in the mock: GET /mock-uploads/:id, which serves uploaded bytes back. Don’t build against it. It doesn’t exist in the real API.

Extending it is a contribution

The mock is deliberately incomplete: handlers are added when a feature needs them. If you hit a 501, adding the handler is a welcome pull request. The server even says so in its own error message.

Contribution workflow

Branches, commits, and what CI checks.