Microservices development in Cursor
PLMicroservices development in Cursor works when Cursor’s Agent sees the service topology and the contracts before it writes code. Project rules describe which service owns what, Plan Mode fixes the boundary, and an OpenAPI or AsyncAPI contract is written and gated first. Each service is accepted on command output: lint, breaking-change diff, contract tests, health check.
This page is for a developer or tech lead who builds services in Cursor every day. The situation is familiar: you ask Agent for a payments service and get a tidy Express app that reads straight from the orders database, invents its own event names, and publishes paymentComplete while two consumers listen for payment.completed. Nothing fails until staging. The fix is not a better prompt; it is context the Agent always loads and gates it cannot talk its way past.
Sources for this page, checked on 2026-09-26: the Cursor features it uses (Rules, Plan Mode, Worktrees) come from Cursor’s documentation as verified on 2026-08-28, and the rule file format follows project rules in Cursor. On 12 October 2026 Cursor’s docs also state that project rules live in .cursor/rules as .mdc files and that Cursor supports AGENTS.md in the project root and subdirectories, which suits per-service instructions: nested files are combined with those in parent directories, and more specific instructions take precedence. UI labels can differ between Cursor 3.x builds; compare them with your own. The oasdiff, @redocly/cli, @asyncapi/cli and @pact-foundation/pact commands were checked against their own repositories and npm on the same date.
What you get from this microservices workflow in Cursor
Section titled “What you get from this microservices workflow in Cursor”- Two kinds of project rule: an always-on topology rule and one file-scoped rule per service, so Agent knows which database and which events belong to which service.
- A Plan Mode prompt that settles a service boundary, its data ownership and its events before any file changes.
- A contract-first loop in which the OpenAPI or AsyncAPI file is written first and blocked in CI when it breaks a consumer.
- A scaffold prompt that copies the conventions of a service you already trust, plus the commands that decide whether the scaffold is done.
- An evidence table that tells a reviewer what to check instead of reading every generated line.
- Recovery steps for the failures specific to multi-service work: shared databases, cycles, event drift and compose networking.
How should Cursor see several services at once?
Section titled “How should Cursor see several services at once?”Agent can only respect a boundary it can see. How you give it that view depends on how your services are stored.
| Repository layout | What Agent sees by default | What to add |
|---|---|---|
Monorepo (orders-service/, payments-service/, …) | Every service | One topology rule plus one file-scoped rule per service, so it does not borrow code across boundaries |
| Polyrepo, a few services you change together | One repository | A multi-root workspace with the services you touch, plus the same rules; see multi-repo workflows in Cursor for the .code-workspace setup |
| Polyrepo, many services owned by other teams | One repository | Copy the other teams’ published contracts into contracts/ and point rules at them instead of opening their code |
Whichever layout you have, write the topology down once. Save it as .cursor/rules/service-topology.mdc:
---description: Service topology, data ownership and communication rules for every taskalwaysApply: true---# Services- api-gateway (Node.js, Express): routing, rate limiting, auth. No database.- orders-service (Node.js, Express): orders. Owns the `orders` Postgres database.- payments-service (Node.js, Express): payments and refunds. Owns the `payments` database.- users-service (Python, FastAPI): accounts and profiles. Owns the `users` database.- inventory-service (Go): stock and reservations. Owns the `inventory` database.
# Hard boundaries- Database per service. Never query another service's database; call its API or consume its events.- Contracts live in <service>/docs/openapi.yml and contracts/events/asyncapi.yml. Change the contract first.- Events are named <entity>.<past-tense-verb> (order.created, payment.completed) and carry event_id and correlation_id.- Every service exposes GET /health and GET /ready and logs JSON with correlation_id.
# Done means- `npx @redocly/cli lint <service>/docs/openapi.yml` passes- the service's own tests pass: `npm test --workspace <service>` (Node services), `pytest` in users-service, `go test ./...` in inventory-serviceThen add one rule per service that loads only when Agent touches that service’s files. Save it as .cursor/rules/payments-service.mdc:
---description: Conventions for payments-serviceglobs: payments-service/**alwaysApply: false---- Stripe webhook signatures are verified with the secret in STRIPE_WEBHOOK_SECRET; never log the payload.- Consumes: order.created. Publishes: payment.initiated, payment.completed, payment.failed, payment.refunded.- Every consumer is idempotent on event_id and sends failures to the dead-letter queue.Keep the always-on rule short, because every session pays for it. Save both as .mdc files, the format used on project rules in Cursor, and do not assume a file loads because it is in .cursor/rules/: run the canary check from that page first. It shows whether a file was loaded at all.
Decide the service boundary in Plan Mode first
Section titled “Decide the service boundary in Plan Mode first”Plan Mode “creates detailed implementation plans before writing any code”, which is what a boundary decision needs: you argue about data ownership while nothing is written yet. Start a new chat, switch it to Plan Mode, and attach the code that currently does the job.
Read the plan, not a diff. The questions that matter are whether any step leaves two services writing the same table and whether every step has a command that proves it worked. If the boundary is still contested, the architecture discussion belongs in architecture planning in Cursor before you build.
Write the contract first, then gate it
Section titled “Write the contract first, then gate it”The contract is the part other teams depend on, so it is the part you verify hardest. Generate it before any handler exists, and follow the naming of a contract that already works.
Events deserve the same treatment. If you keep an AsyncAPI file for RabbitMQ exchanges or Kafka topics, ask Agent to add the new messages there and validate it with npx @asyncapi/cli validate contracts/events/asyncapi.yml (@asyncapi/cli 6.x requires Node 24 or later).
Two commands then turn the contract into a gate that runs on every pull request, whether a person or Agent changed it. Run them in the terminal or in CI:
# 1. The contract is well formed and follows your lint rulesnpx @redocly/cli lint payments-service/docs/openapi.yml
# 2. The change does not break an existing consumer (compare with the main branch)# (skipped for a new service, which has no base on main yet)if git cat-file -e origin/main:payments-service/docs/openapi.yml 2>/dev/null; then git show origin/main:payments-service/docs/openapi.yml > /tmp/openapi-base.yml && oasdiff breaking --fail-on ERR /tmp/openapi-base.yml payments-service/docs/openapi.ymlfioasdiff breaking --fail-on ERR exits with code 1 when it finds a definite breaking change, such as a removed field or a newly required parameter. Install it with brew install oasdiff, go install github.com/oasdiff/oasdiff@latest, or the tufin/oasdiff Docker image. For a brand-new service there is no base on main yet, so the if skips the diff and only the lint runs until the first merge.
Once the contract passes, implementation becomes a narrow task: “implement the handlers in payments-service so they match @payments-service/docs/openapi.yml exactly, using the validation middleware from @orders-service/src/routes/orders.ts”. The contract, not the prompt, now defines the API surface.
Scaffold a new service from one you already trust
Section titled “Scaffold a new service from one you already trust”A generic template misses your conventions. Pointing Agent at a service your team already runs in production gives it concrete middleware, logging and Docker patterns to copy.
The port 3004 is an example; use the one your compose file assigns. The scaffold is done when those three commands pass, the Redocly lint passes, and git diff --stat shows no file outside payments-service/ except the compose entry. That last check is the cheap way to catch Agent “helpfully” editing orders-service.
Choose synchronous calls or events on purpose
Section titled “Choose synchronous calls or events on purpose”Agent can infer a communication style from the wording of your prompt: “Notify the user when an order is placed” reads like a function call and may come back as a direct HTTP call to the notification service. Decide the style yourself and write it into the prompt.
| The caller… | Use | Name it in the prompt as |
|---|---|---|
| Needs the answer before it can respond (stock check during checkout) | Synchronous REST or gRPC with a timeout and a circuit breaker | “Call inventory-service GET /v1/stock with a 2-second timeout” |
| Only needs to know the work will happen (send a receipt) | An event | “Publish order.created; notification-service consumes it” |
| Starts a multi-step business transaction (order, then payment, then stock) | A saga of events with compensation | Build one step at a time; see distributed systems development with AI |
For an event consumer, ask for the failure behaviour and a test that proves it, not only the happy path.
How do you verify a multi-service change without reading every line?
Section titled “How do you verify a multi-service change without reading every line?”A reviewer of a multi-service pull request needs evidence at the boundaries, because that is where the bugs appear. The table below is the evidence bundle to require; each row is a command whose output the pull request links. The idea behind it is covered in reading evidence instead of code.
| Evidence | Command or source | What it proves | Who signs off |
|---|---|---|---|
| Contract is valid | npx @redocly/cli lint … and npx @asyncapi/cli validate … | The contract is well formed | CI |
| No consumer breaks | oasdiff breaking --fail-on ERR against origin/main | No removed fields or newly required inputs | CI; the owning team approves any intended break |
| Consumers still agree | The gateway’s Pact tests (npm test in api-gateway) write a pact file; payments-service’s build replays it with Pact’s Verifier.verifyProvider() and fails on a mismatch | The gateway’s expectations match what payments-service returns | CI on both sides |
| Service behaves | The service’s own test suite, including the idempotency tests above | Handlers and consumers match the contract, including failure paths | CI |
| It runs with its neighbours | docker compose up -d --wait plus one end-to-end request through the gateway | Networking, configuration and health checks work together | The author, with output pasted in the PR |
| It stayed in its lane | git diff --stat origin/main | No edits in services the task did not name | The reviewer, in 10 seconds |
Pact is one dev dependency on both sides: npm install -D @pact-foundation/pact (17.1.4 on npm, checked on 2026-09-26).
Human review then concentrates on the contract diff and the migration order, which are short and consequential. For automated pull request review on top of these gates, see Bugbot; for wiring the commands into a pipeline, see CI/CD with Cursor.
Work on several services in parallel
Section titled “Work on several services in parallel”Cursor’s Worktrees “let Agent work in isolated Git checkouts”, so one agent can build the payments-service handlers while another updates the gateway client, each on its own branch. Give each agent one service and the shared contract as read-only input. Merge the contract change first, then the provider, then the consumers, and rerun the evidence table after each merge. Parallel agents in the Agents Window covers how to start and supervise them.
Debug a request that crosses services
Section titled “Debug a request that crosses services”When a request fails somewhere between the gateway and a database, collect the logs for one correlation ID from each service and let Agent line them up. Use a Plan Mode chat so it investigates and proposes before it edits anything.
A missing service in the timeline, like payments-service here, is itself a finding: either the call never happened or the correlation ID was dropped. For tracing and dashboards that make this routine, see monitoring with Cursor.
What breaks when Agent builds microservices?
Section titled “What breaks when Agent builds microservices?”Agent queries another service’s database. The topology rule did not load, or the prompt attached the other service’s schema file. Recovery: run the canary check from the project rules page, remove the foreign schema from the chat, and restate the boundary: “payments-service reads user data only through GET /v1/users/{id}.” Add a lint rule that fails on imports between service directories so the next attempt is caught mechanically. With ESLint flat config, scope the core no-restricted-imports rule to one service (ESLint 10.11.0, checked on 2026-09-26):
export default [ { files: ['orders-service/**'], rules: { 'no-restricted-imports': ['error', { patterns: [{ group: ['**/payments-service/**'], message: 'Call the payments API or consume its events.' }], }], }, },];Two services now call each other. payments-service calls orders-service, which calls payments-service. Recovery: in a Plan Mode chat, ask Agent to list every outbound call per service from the code and report cycles. Break each cycle with an event, or move the shared step into the service that owns the data.
Event payloads drift between publisher and consumer. A consumer breaks without an error because a field was renamed. Recovery: make the AsyncAPI file the single definition, attach it in every event prompt, and add the schema-rejection test from the consumer prompt to each consumer.
The new container cannot reach its neighbours. Agent wrote a standalone Dockerfile and a compose entry on the default network. Recovery: attach the existing docker-compose.yml, name the network, and require docker compose up -d --wait output before you accept the scaffold.
Agent keeps “fixing” the contract to match its code. A failing Redocly or oasdiff check tempts it to edit the spec. Recovery: say in the prompt that the contract is fixed and only handlers may change, and let CI reject any contract diff that lacks an approval from the owning team.
The context fills up before the real task starts. Four services, their schemas and their compose file are attached to every chat. Recovery: attach only the services the task names, keep other teams’ contracts in contracts/, and let the file-scoped rules load the rest; context management in Cursor shows how to see what a session carries.
How Claude Code and Codex handle the same work
Section titled “How Claude Code and Codex handle the same work”The contract-first loop, the evidence table and the failure modes are the same in every tool; only the context mechanism differs (CLAUDE.md in Claude Code, AGENTS.md in Codex). The cross-tool version of this workflow, with sagas, trace propagation, schema migrations and the relevant MCP servers, is distributed systems development with AI.