Troubleshooting
Common issues and their solutions when using amqp-contract.
Connection Issues
"Connection refused" or "ECONNREFUSED"
Symptoms:
Error: connect ECONNREFUSED 127.0.0.1:5672Cause: RabbitMQ is not running or not accessible at the specified URL.
Solutions:
Check if RabbitMQ is running:
bash# Using Docker docker ps | grep rabbitmq # Check if port 5672 is listening netstat -an | grep 5672 # or lsof -i :5672Start RabbitMQ:
bash# Using Docker docker start rabbitmq # Or create a new container docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4-managementVerify connection URL:
typescript// ✅ Correct format urls: ["amqp://localhost"]; urls: ["amqp://localhost:5672"]; urls: ["amqp://user:password@localhost:5672"]; // ❌ Common mistakes urls: ["localhost"]; // Missing protocol urls: ["amqp://localhost:15672"]; // Wrong port (15672 is for management UI)Check firewall/network:
bash# Test connection telnet localhost 5672 # or nc -zv localhost 5672
"Authentication failed" or "ACCESS_REFUSED"
Symptoms:
Error: ACCESS_REFUSED - Login was refused using authentication mechanism PLAINCause: Invalid credentials or user doesn't exist.
Solutions:
Use correct credentials:
typescript// Default RabbitMQ credentials urls: ["amqp://guest:guest@localhost"]; // Custom credentials urls: ["amqp://myuser:mypassword@localhost"];Create user in RabbitMQ:
bash# Using Docker docker exec rabbitmq rabbitmqctl add_user myuser mypassword docker exec rabbitmq rabbitmqctl set_permissions -p / myuser ".*" ".*" ".*" docker exec rabbitmq rabbitmqctl set_user_tags myuser administratorCheck RabbitMQ Management UI:
- Open http://localhost:15672
- Login with your credentials
- Go to "Admin" → "Users" to verify user exists
"Channel closed" or "Connection closed"
Symptoms:
Error: Channel closed
Error: Connection closed: 320 (CONNECTION-FORCED)Cause: RabbitMQ closed the connection/channel, often due to errors or resource limits.
Solutions:
Check RabbitMQ logs:
bash# Using Docker docker logs rabbitmqVerify resource limits:
- Check memory and disk space in RabbitMQ Management UI
- Look for alarms under "Admin" tab
Handle errors properly:
typescriptconst result = await client.publish("sendEmail", message); result.match({ Ok: () => console.log("Published"), Error: (error) => { console.error("Failed:", error); // Don't ignore errors! }, });Graceful shutdown:
typescriptprocess.on("SIGINT", async () => { await worker.close(); await client.close(); process.exit(0); });
TypeScript Errors
"Type 'X' is not assignable to type 'Y'"
Symptoms:
Type '{ to: string; subject: string; }' is not assignable to type 'EmailMessage'.
Property 'body' is missing.Cause: Message doesn't match the schema defined in your contract.
Solution: Provide all required fields:
// ❌ Missing required field
await client.publish("sendEmail", {
to: "user@example.com",
subject: "Hello",
// Missing 'body'
});
// ✅ All required fields
await client.publish("sendEmail", {
to: "user@example.com",
subject: "Hello",
body: "Welcome!",
});"Property 'X' does not exist on type 'Y'"
Symptoms:
Property 'orderId' does not exist on type 'never'.Cause: TypeScript cannot infer types properly from the contract.
Solutions:
Ensure contract is properly typed:
typescript// ✅ Export contract as const export const contract = defineContract({ // ... }); // ❌ Don't use 'any' or lose type information export const contract: any = defineContract({ // ... });Use correct type inference:
typescriptimport type { ClientInferPublisherInput } from "@amqp-contract/contract"; import { contract } from "./contract.js"; type EmailInput = ClientInferPublisherInput<typeof contract, "sendEmail">;Check consumer handler types:
typescript// ✅ Message is automatically typed handlers: { processEmail: async (message) => { console.log(message.to); // Type-safe! }, }
"Cannot find module" or "Module not found"
Symptoms:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module './contract'Cause: Missing .js extension in imports (required for ESM).
Solution: Always use .js extensions:
// ❌ Missing extension
import { contract } from "./contract";
// ✅ With extension
import { contract } from "./contract.js";TIP
Even though your file is contract.ts, you must import it as contract.js when using ESM!
"moduleResolution" or "module" errors
Symptoms:
Module resolution kind 'Node' is not supported for ES6 module output.Cause: Incorrect TypeScript configuration.
Solution: Update tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true
}
}Validation Errors
"Validation failed: expected string, received number"
Symptoms:
Error: Validation failed: [
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"path": ["orderId"]
}
]Cause: Message payload doesn't match the Zod/Valibot/ArkType schema.
Solution: Ensure data types match the schema:
// Schema
const orderMessage = defineMessage(
z.object({
orderId: z.string(),
amount: z.number(),
}),
);
// ❌ Wrong types
await client.publish("orderCreated", {
orderId: 123, // Should be string!
amount: "99.99", // Should be number!
});
// ✅ Correct types
await client.publish("orderCreated", {
orderId: "ORD-123", // String
amount: 99.99, // Number
});"Required field missing"
Symptoms:
Error: Validation failed: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["customerId"]
}
]Cause: Publishing incomplete message with missing required fields.
Solution: Provide all required fields:
// Schema
const orderMessage = defineMessage(
z.object({
orderId: z.string(),
customerId: z.string(),
amount: z.number(),
}),
);
// ❌ Missing customerId
await client.publish("orderCreated", {
orderId: "ORD-123",
amount: 99.99,
});
// ✅ All fields present
await client.publish("orderCreated", {
orderId: "ORD-123",
customerId: "CUST-456",
amount: 99.99,
});"Additional property not allowed"
Symptoms:
Error: Validation failed: Unrecognized key(s) in object: 'extraField'Cause: Sending fields not defined in schema.
Solution: Remove extra fields or update schema:
// Option 1: Remove extra field
await client.publish("orderCreated", {
orderId: "ORD-123",
amount: 99.99,
// extraField: "value", // Remove this
});
// Option 2: Update schema to allow it
const orderMessage = defineMessage(
z.object({
orderId: z.string(),
amount: z.number(),
metadata: z.record(z.unknown()).optional(), // Allow extra data
}),
);Performance Issues
High memory usage
Symptoms:
- Application memory usage grows over time
- RabbitMQ shows high memory consumption
- Out of memory errors
Causes & Solutions:
Too many concurrent connections:
typescript// ❌ Creating new connection for each operation async function publishMessage() { const client = await TypedAmqpClient.create({ contract, urls: ["amqp://localhost"], }).resultToPromise(); await client.publish("sendEmail", message); await client.close(); } // ✅ Reuse connection const client = await TypedAmqpClient.create({ contract, urls: ["amqp://localhost"], }).resultToPromise(); async function publishMessage() { await client.publish("sendEmail", message); }Not closing connections:
typescript// ✅ Always close connections process.on("SIGINT", async () => { await worker.close(); await client.close(); process.exit(0); });Large message payloads:
typescript// Consider using message compression // See: /guide/message-compression
Slow message processing
Symptoms:
- Messages accumulate in queues
- Consumer can't keep up with publisher
- High CPU usage
Causes & Solutions:
Synchronous/blocking handlers:
typescript// ❌ Blocking operation handlers: { processOrder: async (message) => { const result = await fetch("http://slow-api.com/process"); // Slow! await processResult(result); }, } // ✅ Use prefetch to control concurrency const worker = await TypedAmqpWorker.create({ contract, handlers: { /* ... */ }, channelOptions: { prefetch: 10, // Process up to 10 messages concurrently }, }).resultToPromise();Heavy computation in handlers:
typescript// ✅ Offload heavy work handlers: { processImage: async (message) => { // Queue heavy work to worker pool await jobQueue.add("process-image", message); }, }Inefficient queries:
typescript// ❌ N+1 query problem handlers: { processOrder: async (message) => { for (const item of message.items) { await db.query("SELECT * FROM products WHERE id = ?", [item.id]); } }, } // ✅ Batch queries handlers: { processOrder: async (message) => { const ids = message.items.map(item => item.id); await db.query("SELECT * FROM products WHERE id IN (?)", [ids]); }, }
Connection timeouts
Symptoms:
Error: Connection timeoutCause: Network latency or RabbitMQ overload.
Solutions:
Increase timeout:
typescriptconst client = await TypedAmqpClient.create({ contract, urls: ["amqp://localhost"], socketOptions: { timeout: 10000, // 10 seconds }, }).resultToPromise();Use connection pooling:
Check RabbitMQ performance:
- Monitor queue depths
- Check message rates
- Review resource usage
RabbitMQ Configuration
Queue not declared
Symptoms:
Error: Queue 'order-processing' not foundCause: Contract definition mismatch or queue not created.
Solutions:
Ensure queue is in contract:
typescriptconst contract = defineContract({ queues: { orderProcessing: defineQueue("order-processing", { durable: true, }), }, // ... });Check RabbitMQ Management UI:
- Go to "Queues" tab
- Verify queue exists
- Check queue properties match contract
Let amqp-contract create resources:
typescript// Resources are automatically created when client/worker starts const client = await TypedAmqpClient.create({ contract, urls: ["amqp://localhost"], }).resultToPromise();
Messages not routing
Symptoms:
- Messages published successfully but not received
- Queue remains empty
Causes & Solutions:
Routing key mismatch:
typescript// Publisher const publisher = definePublisher(exchange, message, { routingKey: "order.created", // ⚠️ Must match binding }); // Binding const binding = defineQueueBinding(queue, exchange, { routingKey: "order.created", // ⚠️ Must match publisher });Wrong exchange type:
typescript// ❌ Direct exchange with topic pattern const exchange = defineExchange("orders", "direct"); // ... routingKey: "order.*"; // Won't work with direct exchange! // ✅ Use topic exchange for patterns const exchange = defineExchange("orders", "topic"); // ... routingKey: "order.*"; // Works with topic exchange!Verify in RabbitMQ Management UI:
- Go to exchange → Bindings
- Verify queue is bound with correct routing key
- Use "Publish message" to test routing
Exchange or queue already exists with different properties
Symptoms:
Error: PRECONDITION_FAILED - inequivalent arg 'durable' for exchange 'orders'Cause: Trying to declare exchange/queue with properties that differ from existing one.
Solutions:
Delete and recreate:
bash# Using RabbitMQ Management UI: # - Go to Exchanges/Queues tab # - Delete the resource # - Restart your applicationMatch existing properties:
typescript// Find existing properties in RabbitMQ Management UI // Update contract to match: const exchange = defineExchange("orders", "topic", { durable: true, // Match existing });Use different names:
typescript// If you can't delete, use a new name const exchange = defineExchange("orders-v2", "topic", { durable: true, });
Still Having Issues?
If your problem isn't listed here:
Check GitHub Issues:
Review Documentation:
Check Examples:
Debug Mode:
typescript// Enable debug logging (if available in your logger) process.env.DEBUG = "amqp-contract:*";RabbitMQ Logs:
bash# Check RabbitMQ logs for errors docker logs rabbitmq
Need More Help?
When asking for help, please provide:
- amqp-contract version (check
package.json) - Node.js version (
node --version) - TypeScript version (
npx tsc --version) - RabbitMQ version
- Complete error message and stack trace
- Minimal reproduction code