Comprehensive Rules for designing, implementing and maintaining high-quality asynchronous code across JavaScript/TypeScript (Node.js), Python (asyncio/FastAPI) and Rust (Tokio).
Your backend is bottlenecked by blocking operations, race conditions are haunting your production logs, and you're spending more time debugging asynchronous workflows than building features. Sound familiar?
Backend developers face a brutal reality: modern applications demand high concurrency, but async code is notoriously difficult to get right. You're juggling:
The stakes are high. A single blocking operation can tank your API response times. One unhandled async error can crash your entire service.
These Cursor Rules encode the hard-won lessons from building production-grade concurrent systems across JavaScript/TypeScript, Python, and Rust. They transform how you write, test, and maintain async code by embedding proven patterns directly into your development workflow.
What makes this different? Instead of generic async guidance, these rules provide specific, actionable patterns for real backend scenarios - from API handlers that need sub-100ms response times to message processors handling thousands of concurrent operations.
// Blocks event loop, loses error context, no timeout handling
app.get('/user/:id', async (req, res) => {
const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
const orders = await api.get(`/orders/user/${req.params.id}`);
res.json({ user, orders });
});
// Parallel execution, comprehensive error handling, timeouts
app.get('/user/:id', asyncHandler(async (req, res) => {
try {
const [user, orders] = await Promise.all([
userRepo.fetch(req.params.id),
orderRepo.listByUser(req.params.id)
]);
res.json({ user, orders });
} catch (err) {
throw new APIError('Failed to load user data', {
cause: err,
correlationId: req.correlationId
});
}
}));
Result: Response time drops from 200ms+ to sub-50ms while adding bulletproof error handling.
# No backpressure, errors crash the service, no retry logic
async def process_message(message):
await heavy_computation(message.data)
await db.save(result)
# Structured concurrency, retries, poison message handling
async def process_message(message: Message) -> None:
async with asyncio.TaskGroup() as group:
try:
result = await asyncio.wait_for(
heavy_computation(message.data),
timeout=30.0
)
await db.save_with_retry(result, max_attempts=3)
except asyncio.TimeoutError:
await dead_letter_queue.send(message)
raise ProcessingError(f"Timeout processing {message.id}")
Result: Zero message loss, predictable processing times, automatic failure recovery.
.cursor-rules fileStart with your highest-traffic async operations:
The rules include deterministic testing patterns:
// Before: Flaky time-dependent tests
test('processes messages', async () => {
await processMessage(msg);
await new Promise(resolve => setTimeout(resolve, 100)); // 🚫 Flaky
expect(result).toBeDefined();
});
// After: Deterministic async testing
test('processes messages', async () => {
const clock = sinon.useFakeTimers();
const promise = processMessage(msg);
clock.tick(1000);
const result = await promise;
expect(result).toBeDefined();
clock.restore();
});
Built-in instrumentation patterns help you measure improvement:
Stop fighting async complexity. These rules don't just prevent bugs - they fundamentally change how you approach concurrent programming. You'll write async code with the confidence of synchronous code, knowing every boundary is protected, every error is handled, and every operation is resilient.
The patterns work across your entire tech stack. Whether you're building Node.js APIs, Python data processing pipelines, or Rust microservices, these rules adapt to your language while maintaining consistent async architecture principles.
Ready to transform your async development workflow? Your production services - and your on-call rotation - will thank you.
You are an expert in JavaScript/TypeScript (Node.js 20+), Python 3.12+ (asyncio/FastAPI), and Rust 1.75+ (Tokio).
Key Principles
- Embrace non-blocking I/O; never allow the main event loop or thread pool to starve.
- Prefer async/await for linear readability; fall back to raw Promises/Futures only for advanced orchestration.
- Fail fast: surface errors early, provide context, and log with correlation IDs.
- Treat every asynchronous boundary as a potential failure point; design retries, circuit breakers and idempotency from day one.
- Keep CPU-bound work off the event loop using Worker Threads (Node.js), ProcessPoolExecutor (Python) or `tokio::spawn_blocking` (Rust).
- Use structured logging (pino, winston, loguru, tracing) with milliseconds timestamps.
- Persist progress for long-running tasks so that restarts are safe and resumable (sagas / compensation flow).
- Write deterministic, time-boxed tests; never rely on real timeouts.
JavaScript / TypeScript (Node.js)
- Always mark asynchronous top-level functions `async`; return `Promise<T>` instead of accepting callbacks.
- Name functions with a verb + Async suffix only when both sync & async variants exist (e.g., `readFile` vs `readFileAsync`).
- Await immediately after initiating asynchronous work unless you intentionally want concurrency.
```ts
// GOOD – parallel execution
const [user, orders] = await Promise.all([
userRepo.fetch(id),
orderRepo.listByUser(id)
]);
```
- Use `try { … } catch (err) { … }` around every `await`; re-throw with additional context not to swallow stack traces.
- Chain `.catch()` on bare Promises returned from non-awaited functions to avoid the "unhandledRejection" process event.
- For Node.js streams, wrap in `pipeline`/`promisify` to ensure back-pressure and proper error bubbling.
- CPU-intensive loops → `new Worker('./worker.ts')` or the built-in `worker_threads` API.
- Lint rules: `eslint-plugin-promise` (no-return-await, prefer-await-to-callbacks), `@typescript-eslint/await-thenable`.
Python (asyncio / FastAPI)
- Use `async def` for coroutines; never call a coroutine without `await` or `asyncio.create_task()`.
- Gather independent tasks with explicit timeouts:
```py
results = await asyncio.gather(*tasks, return_exceptions=False)
```
- Use `AnyIO` or `asyncio.TaskGroup` for structured concurrency; cancel the entire group on first error.
- In FastAPI endpoints, never perform blocking I/O; delegate to `run_in_threadpool` for legacy libs.
- Exception handling middleware must convert uncaught exceptions into JSON error responses with trace IDs.
- Prefer `httpx.AsyncClient` for outbound calls; configure `timeout=...` for every request.
Rust (Tokio)
- Spawn lightweight tasks with `tokio::spawn` only when logical concurrency is required; otherwise keep functions synchronous.
- Use `?` operator inside `async fn` to propagate errors quickly.
- Select over multiple futures with `tokio::select!` to implement cancellation/timeouts.
- Channel back-pressure via `tokio::sync::mpsc` with bounded capacity; always handle the `SendError`.
- Wrap CPU-heavy work in `spawn_blocking` or delegate to `rayon`.
Error Handling and Validation
- Position input validation at function boundaries; use Zod (TS), Pydantic (Py), or `validator` crate (Rust).
- Early-return on validation failure.
- Catch-wrap-rethrow pattern:
```ts
try { … }
catch (err) {
throw new DataStoreError('Failed to persist invoice', { cause: err });
}
```
- Central error handler logs stack, correlation ID, and sends user-friendly message.
- Implement exponential back-off retries (max 3 attempts, jittered) for idempotent operations; no retry for POST without idempotency key.
- Poison messages routed to a dead-letter queue (DLQ) after N retries.
Framework-Specific Rules
Express (Node.js)
- Wrap every route handler in an `asyncHandler` that forwards errors to `next(err)`.
- Never send partial responses; buffer entire result or stream with proper back-pressure.
FastAPI
- Use dependency injection to inject DB sessions; mark them `async` when using async drivers.
- Add `@app.middleware("http")` for logging latency and capturing exceptions.
Tokio
- Configure runtime with `worker_threads = num_cpus * 2` for IO-heavy services.
- Use `tokio::time::timeout` on every outbound IO.
Additional Sections
Testing
- Use fake timers (lolex/sinon, `freezegun`, `tokio::time::pause`) to avoid real sleep.
- Assert concurrency with promise/future inspectors; ensure no unhandled rejections.
Performance & Scalability
- Batch DB requests and API calls.
- Monitor event loop lag (`perf_hooks.monitorEventLoopDelay()`, `asyncio.get_event_loop().slow_callback_duration`).
- Instrument with OpenTelemetry spans across async boundaries.
Security
- Sanitize all user input before asynchronous DB or file operations.
- Propagate authentication context (JWT, mTLS cert) across async tasks; disallow orphan tasks without context.
- Lock critical sections with distributed locks (e.g., Redlock) to prevent concurrent mutation.