JSPM

  • Created
  • Published
  • Downloads 4091
  • Score
    100M100P100Q120748F
  • License MIT

SNS/SQS/S3 emulator

Package Exports

  • fauxqs

Readme

fauxqs

Local SNS/SQS/S3 emulator for development and testing. Point your @aws-sdk/client-sqs, @aws-sdk/client-sns, and @aws-sdk/client-s3 clients at fauxqs instead of real AWS.

All state is in-memory. No persistence, no external storage dependencies.

Table of Contents

Installation

npm install fauxqs

Usage

Running the server

npx fauxqs

The server starts on port 4566 and handles SQS, SNS, and S3 on a single endpoint.

Environment variables

Variable Description Default
FAUXQS_PORT Port to listen on 4566
FAUXQS_HOST Host for queue URLs (sqs.<region>.<host> format) localhost
FAUXQS_DEFAULT_REGION Fallback region for ARNs and URLs us-east-1
FAUXQS_LOGGER Enable request logging (true/false) true
FAUXQS_INIT Path to a JSON init config file (see Init config file) (none)
FAUXQS_PORT=3000 FAUXQS_INIT=init.json npx fauxqs

A health check is available at GET /health.

Running in the background

To keep fauxqs running while you work on your app or run tests repeatedly, start it as a background process:

npx fauxqs &

Or in a separate terminal:

npx fauxqs

All state accumulates in memory across requests, so queues, topics, and objects persist until the server is stopped.

To stop the server:

# If backgrounded in the same shell
kill %1

# Cross-platform, by port
npx cross-port-killer 4566

Running in Docker Compose

Use the node:24-alpine image and mount a JSON init config to pre-create resources on startup:

// scripts/fauxqs/init.json
{
  "queues": [
    {
      "name": "my-queue.fifo",
      "attributes": { "FifoQueue": "true", "ContentBasedDeduplication": "true" }
    },
    { "name": "my-dlq" }
  ],
  "topics": [{ "name": "my-events" }],
  "subscriptions": [{ "topic": "my-events", "queue": "my-dlq" }],
  "buckets": ["my-uploads"]
}
# docker-compose.yml
services:
  fauxqs:
    image: node:24-alpine
    working_dir: /app
    command: npx --yes fauxqs@1.3.5
    ports:
      - "4566:4566"
    environment:
      - FAUXQS_HOST=localhost
      - FAUXQS_INIT=/app/init.json
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:4566/health || exit 1"]
      interval: 2s
      timeout: 5s
      retries: 10
    volumes:
      - ./scripts/fauxqs/init.json:/app/init.json

  app:
    # ...
    depends_on:
      fauxqs:
        condition: service_healthy

Other containers reference fauxqs using the Docker service name (http://fauxqs:4566). The init config file creates all queues, topics, subscriptions, and buckets before the healthcheck passes, so dependent services start only after resources are ready.

Configuring AWS SDK clients

Point your SDK clients at the local server:

import { SQSClient } from "@aws-sdk/client-sqs";
import { SNSClient } from "@aws-sdk/client-sns";
import { S3Client } from "@aws-sdk/client-s3";
import { createLocalhostHandler } from "fauxqs";

const sqsClient = new SQSClient({
  endpoint: "http://localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
});

const snsClient = new SNSClient({
  endpoint: "http://localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
});

const s3Client = new S3Client({
  endpoint: "http://s3.localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
  requestHandler: createLocalhostHandler(),
});

Any credentials are accepted and never validated.

Programmatic usage

You can also embed fauxqs directly in your test suite:

import { startFauxqs } from "fauxqs";

const server = await startFauxqs({ port: 4566, logger: false });

console.log(server.address); // "http://127.0.0.1:4566"
console.log(server.port);    // 4566

// point your SDK clients at server.address

// clean up when done
await server.stop();

Pass port: 0 to let the OS assign a random available port (useful in tests).

Programmatic state setup

The server object exposes methods for pre-creating resources without going through the SDK:

const server = await startFauxqs({ port: 0, logger: false });

// Create individual resources
server.createQueue("my-queue");
server.createQueue("my-dlq", {
  attributes: { VisibilityTimeout: "60" },
  tags: { env: "test" },
});
server.createTopic("my-topic");
server.subscribe({ topic: "my-topic", queue: "my-queue" });
server.createBucket("my-bucket");

// Or create everything at once
server.setup({
  queues: [
    { name: "orders" },
    { name: "notifications", attributes: { DelaySeconds: "5" } },
  ],
  topics: [{ name: "events" }],
  subscriptions: [
    { topic: "events", queue: "orders" },
    { topic: "events", queue: "notifications" },
  ],
  buckets: ["uploads", "exports"],
});

// Reset all state between tests
server.purgeAll();

Init config file

Create a JSON file to pre-create resources on startup. The file is validated on load — malformed configs produce a clear error instead of silent failures.

{
  "queues": [
    { "name": "orders" },
    { "name": "orders-dlq" },
    { "name": "orders.fifo", "attributes": { "FifoQueue": "true", "ContentBasedDeduplication": "true" } }
  ],
  "topics": [
    { "name": "events" }
  ],
  "subscriptions": [
    { "topic": "events", "queue": "orders" }
  ],
  "buckets": ["uploads", "exports"]
}

Pass it via the FAUXQS_INIT environment variable or the init option:

FAUXQS_INIT=init.json npx fauxqs
const server = await startFauxqs({ init: "init.json" });
// or inline:
const server = await startFauxqs({
  init: { queues: [{ name: "my-queue" }], buckets: ["my-bucket"] },
});

Init config schema reference

All top-level fields are optional. Resources are created in dependency order: queues, topics, subscriptions, buckets.

queues

Array of queue objects.

Field Type Required Description
name string Yes Queue name. Use .fifo suffix for FIFO queues.
attributes Record<string, string> No Queue attributes (see table below).
tags Record<string, string> No Key-value tags for the queue.

Supported queue attributes:

Attribute Default Range / Values
VisibilityTimeout "30" 043200 (seconds)
DelaySeconds "0" 0900 (seconds)
MaximumMessageSize "1048576" 10241048576 (bytes)
MessageRetentionPeriod "345600" 601209600 (seconds)
ReceiveMessageWaitTimeSeconds "0" 020 (seconds)
RedrivePolicy JSON string: {"deadLetterTargetArn": "arn:...", "maxReceiveCount": "5"}
Policy Queue policy JSON string (stored, not enforced)
KmsMasterKeyId KMS key ID (stored, no actual encryption)
KmsDataKeyReusePeriodSeconds KMS data key reuse period (stored, no actual encryption)
FifoQueue "true" for FIFO queues (queue name must end with .fifo)
ContentBasedDeduplication "true" or "false" (FIFO queues only)

Example:

{
  "queues": [
    {
      "name": "orders",
      "attributes": { "VisibilityTimeout": "60", "DelaySeconds": "5" },
      "tags": { "env": "staging", "team": "platform" }
    },
    {
      "name": "orders-dlq"
    },
    {
      "name": "orders.fifo",
      "attributes": {
        "FifoQueue": "true",
        "ContentBasedDeduplication": "true"
      }
    },
    {
      "name": "retry-queue",
      "attributes": {
        "RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:000000000000:orders-dlq\",\"maxReceiveCount\":\"3\"}"
      }
    }
  ]
}
topics

Array of topic objects.

Field Type Required Description
name string Yes Topic name. Use .fifo suffix for FIFO topics.
attributes Record<string, string> No Topic attributes (e.g., DisplayName).
tags Record<string, string> No Key-value tags for the topic.

Example:

{
  "topics": [
    {
      "name": "events",
      "attributes": { "DisplayName": "Application Events" },
      "tags": { "env": "staging" }
    },
    {
      "name": "events.fifo",
      "attributes": { "FifoQueue": "true", "ContentBasedDeduplication": "true" }
    }
  ]
}
subscriptions

Array of subscription objects. Referenced topics and queues must be defined in the same config (or already exist on the server).

Field Type Required Description
topic string Yes Topic name (not ARN) to subscribe to.
queue string Yes Queue name (not ARN) to deliver messages to.
attributes Record<string, string> No Subscription attributes (see table below).

Supported subscription attributes:

Attribute Values Description
RawMessageDelivery "true" / "false" Deliver the raw message body instead of the SNS envelope JSON.
FilterPolicy JSON string SNS filter policy for message filtering (e.g., "{\"color\": [\"blue\"]}")
FilterPolicyScope "MessageAttributes" / "MessageBody" Whether the filter policy applies to message attributes or body. Defaults to MessageAttributes.
RedrivePolicy JSON string Subscription-level dead-letter queue config.
DeliveryPolicy JSON string Delivery retry policy (stored, not enforced).
SubscriptionRoleArn ARN string IAM role ARN for delivery (stored, not enforced).

Example:

{
  "subscriptions": [
    {
      "topic": "events",
      "queue": "orders",
      "attributes": {
        "RawMessageDelivery": "true",
        "FilterPolicy": "{\"eventType\": [\"order.created\", \"order.updated\"]}"
      }
    },
    {
      "topic": "events",
      "queue": "notifications"
    }
  ]
}
buckets

Array of bucket name strings.

{
  "buckets": ["uploads", "exports", "temp"]
}

Message spy

MessageSpyReader lets you await specific events flowing through SQS, SNS, and S3 in your tests — without polling queues yourself. Inspired by HandlerSpy from message-queue-toolkit.

Enable it with the messageSpies option:

const server = await startFauxqs({ port: 0, logger: false, messageSpies: true });

The spy tracks events across all three services using a discriminated union on service:

SQS events (service: 'sqs'):

  • published — message was enqueued (via SendMessage, SendMessageBatch, or SNS fan-out)
  • consumed — message was deleted (via DeleteMessage / DeleteMessageBatch)
  • dlq — message exceeded maxReceiveCount and was moved to a dead-letter queue

SNS events (service: 'sns'):

  • published — message was published to a topic (before fan-out to SQS subscriptions)

S3 events (service: 's3'):

  • uploaded — object was put (PutObject or CompleteMultipartUpload)
  • downloaded — object was retrieved (GetObject)
  • deleted — object was deleted (DeleteObject, only when key existed)
  • copied — object was copied (CopyObject; also emits uploaded for the destination)
Awaiting messages
// Wait for a specific SQS message (resolves immediately if already in buffer)
const msg = await server.spy.waitForMessage(
  (m) => m.service === "sqs" && m.body === "order.created" && m.queueName === "orders",
  "published",
);

// Wait by SQS message ID
const msg = await server.spy.waitForMessageWithId(messageId, "consumed");

// Partial object match (deep-equal on specified fields)
const msg = await server.spy.waitForMessage({ service: "sqs", queueName: "orders", status: "published" });

// Wait for an SNS publish event
const msg = await server.spy.waitForMessage({ service: "sns", topicName: "my-topic", status: "published" });

// Wait for an S3 upload event
const msg = await server.spy.waitForMessage({ service: "s3", bucket: "my-bucket", key: "file.txt", status: "uploaded" });

waitForMessage checks the buffer first (retroactive resolution). If no match is found, it returns a Promise that resolves when a matching message arrives.

Timeout

All waitForMessage and waitForMessageWithId calls accept an optional timeout parameter (ms) as the third argument. If no matching message arrives in time, the promise rejects with a timeout error — preventing tests from hanging indefinitely:

// Reject after 2 seconds if no match
const msg = await server.spy.waitForMessage(
  { service: "sqs", queueName: "orders" },
  "published",
  2000,
);

// Also works with waitForMessageWithId
const msg = await server.spy.waitForMessageWithId(messageId, "consumed", 5000);
Waiting for multiple messages

waitForMessages collects count matching messages before resolving. It checks the buffer first, then awaits future arrivals:

// Wait for 3 messages on the orders queue
const msgs = await server.spy.waitForMessages(
  { service: "sqs", queueName: "orders" },
  { count: 3, status: "published", timeout: 5000 },
);
// msgs.length === 3

If the timeout expires before enough messages arrive, the promise rejects with a message showing how many were collected (e.g., "collected 1/3").

Negative assertions

expectNoMessage asserts that no matching message appears within a time window. Useful for verifying that filter policies dropped a message or that a side effect did not occur:

// Assert no message was delivered to the wrong queue (waits 200ms by default)
await server.spy.expectNoMessage({ service: "sqs", queueName: "wrong-queue" });

// Custom window and status filter
await server.spy.expectNoMessage(
  { service: "sqs", queueName: "orders" },
  { status: "dlq", within: 500 },
);

If a matching message is already in the buffer, expectNoMessage rejects immediately. If one arrives during the wait, it rejects with "matching message arrived during wait".

Synchronous check
const msg = server.spy.checkForMessage(
  (m) => m.service === "sqs" && m.queueName === "my-queue",
  "published",
);
// returns SpyMessage | undefined
Buffer management
// Get all tracked messages (oldest to newest)
const all = server.spy.getAllMessages();

// Clear buffer and reject pending waiters
server.spy.clear();

The buffer defaults to 100 messages (FIFO eviction). Configure with:

const server = await startFauxqs({
  messageSpies: { bufferSize: 500 },
});
Types

server.spy returns a MessageSpyReader — a read-only interface that exposes query and await methods but not internal mutation (e.g. recording new events):

interface MessageSpyReader {
  waitForMessage(filter: MessageSpyFilter, status?: string, timeout?: number): Promise<SpyMessage>;
  waitForMessageWithId(messageId: string, status?: string, timeout?: number): Promise<SpyMessage>;
  waitForMessages(filter: MessageSpyFilter, options: WaitForMessagesOptions): Promise<SpyMessage[]>;
  expectNoMessage(filter: MessageSpyFilter, options?: ExpectNoMessageOptions): Promise<void>;
  checkForMessage(filter: MessageSpyFilter, status?: string): SpyMessage | undefined;
  getAllMessages(): SpyMessage[];
  clear(): void;
}

interface WaitForMessagesOptions {
  count: number;
  status?: string;
  timeout?: number;
}

interface ExpectNoMessageOptions {
  status?: string;
  within?: number; // ms, defaults to 200
}

SpyMessage is a discriminated union:

interface SqsSpyMessage {
  service: "sqs";
  queueName: string;
  messageId: string;
  body: string;
  messageAttributes: Record<string, MessageAttributeValue>;
  status: "published" | "consumed" | "dlq";
  timestamp: number;
}

interface SnsSpyMessage {
  service: "sns";
  topicArn: string;
  topicName: string;
  messageId: string;
  body: string;
  messageAttributes: Record<string, MessageAttributeValue>;
  status: "published";
  timestamp: number;
}

interface S3SpyEvent {
  service: "s3";
  bucket: string;
  key: string;
  status: "uploaded" | "downloaded" | "deleted" | "copied";
  timestamp: number;
}

type SpyMessage = SqsSpyMessage | SnsSpyMessage | S3SpyEvent;
Spy disabled by default

Accessing server.spy when messageSpies is not set throws an error. There is no overhead on the message flow when spies are disabled.

Queue inspection

Non-destructive inspection of SQS queue state — see all messages (ready, in-flight, and delayed) without consuming them or affecting visibility timeouts.

Programmatic API
const result = server.inspectQueue("my-queue");
// result is undefined if queue doesn't exist
if (result) {
  console.log(result.name);           // "my-queue"
  console.log(result.url);            // "http://sqs.us-east-1.localhost:4566/000000000000/my-queue"
  console.log(result.arn);            // "arn:aws:sqs:us-east-1:000000000000:my-queue"
  console.log(result.attributes);     // { VisibilityTimeout: "30", ... }
  console.log(result.messages.ready);    // messages available for receive
  console.log(result.messages.delayed);  // messages waiting for delay to expire
  console.log(result.messages.inflight); // received but not yet deleted
  // Each inflight entry includes: { message, receiptHandle, visibilityDeadline }
}
HTTP endpoints
# List all queues with summary counts
curl http://localhost:4566/_fauxqs/queues
# [{ "name": "my-queue", "approximateMessageCount": 5, "approximateInflightCount": 2, "approximateDelayedCount": 0, ... }]

# Inspect a specific queue (full state)
curl http://localhost:4566/_fauxqs/queues/my-queue
# { "name": "my-queue", "messages": { "ready": [...], "delayed": [...], "inflight": [...] }, ... }

Returns 404 for non-existent queues. Inspection never modifies queue state — messages remain exactly where they are.

Configurable queue URL host

Queue URLs use the AWS-style sqs.<region>.<host> format. The host defaults to localhost, producing URLs like http://sqs.us-east-1.localhost:4566/000000000000/myQueue.

To override the host (e.g., for a custom domain):

import { startFauxqs } from "fauxqs";

const server = await startFauxqs({ port: 4566, host: "myhost.local" });
// Queue URLs: http://sqs.us-east-1.myhost.local:4566/000000000000/myQueue

This also works with buildApp:

import { buildApp } from "fauxqs";

const app = buildApp({ host: "myhost.local" });

The configured host ensures queue URLs are consistent across all creation paths (init config, programmatic API, and SDK requests), regardless of the request's Host header.

Region

The region used in ARNs and queue URLs is automatically detected from the SDK client's Authorization header. If your SDK client is configured with region: "eu-west-1", fauxqs will use that region in all generated ARNs and URLs.

If the region cannot be resolved from request headers (e.g., requests without AWS SigV4 signing), the defaultRegion option is used as a fallback (defaults to "us-east-1"):

const server = await startFauxqs({ defaultRegion: "eu-west-1" });

Supported API Actions

SQS

Action Supported
CreateQueue Yes
DeleteQueue Yes
GetQueueUrl Yes
ListQueues Yes
GetQueueAttributes Yes
SetQueueAttributes Yes
PurgeQueue Yes
SendMessage Yes
SendMessageBatch Yes
ReceiveMessage Yes
DeleteMessage Yes
DeleteMessageBatch Yes
ChangeMessageVisibility Yes
ChangeMessageVisibilityBatch Yes
TagQueue Yes
UntagQueue Yes
ListQueueTags Yes
AddPermission No
RemovePermission No
ListDeadLetterSourceQueues No
StartMessageMoveTask No
CancelMessageMoveTask No
ListMessageMoveTasks No

SNS

Action Supported
CreateTopic Yes
DeleteTopic Yes
ListTopics Yes
GetTopicAttributes Yes
SetTopicAttributes Yes
Subscribe Yes
Unsubscribe Yes
ConfirmSubscription Yes
ListSubscriptions Yes
ListSubscriptionsByTopic Yes
GetSubscriptionAttributes Yes
SetSubscriptionAttributes Yes
Publish Yes
PublishBatch Yes
TagResource Yes
UntagResource Yes
ListTagsForResource Yes
AddPermission No
RemovePermission No
GetDataProtectionPolicy No
PutDataProtectionPolicy No

Platform application, SMS, and phone number actions are not supported.

S3

Action Supported
CreateBucket Yes
HeadBucket Yes
ListObjects Yes
ListObjectsV2 Yes
CopyObject Yes
PutObject Yes
GetObject Yes
DeleteObject Yes
HeadObject Yes
DeleteObjects Yes
DeleteBucket Yes
ListBuckets Yes
CreateMultipartUpload Yes
UploadPart Yes
CompleteMultipartUpload Yes
AbortMultipartUpload Yes
ListObjectVersions No
GetBucketLocation No

Bucket configuration (CORS, lifecycle, encryption, replication, etc.), ACLs, versioning, tagging, and other management actions are not supported.

STS

Action Supported
GetCallerIdentity Yes
AssumeRole No
GetSessionToken No
GetFederationToken No

Returns a mock identity with account 000000000000 and ARN arn:aws:iam::000000000000:root. This allows tools like Terraform and the AWS CLI that call sts:GetCallerIdentity on startup to work without errors. Other STS actions are not supported.

SQS Features

  • Message attributes with MD5 checksums matching the AWS algorithm
  • Visibility timeout — messages become invisible after receive and reappear after timeout
  • Delay queues — per-queue default delay and per-message delay overrides
  • Long pollingWaitTimeSeconds on ReceiveMessage blocks until messages arrive or timeout
  • Dead letter queues — messages exceeding maxReceiveCount are moved to the configured DLQ
  • Batch operations — SendMessageBatch, DeleteMessageBatch, ChangeMessageVisibilityBatch with entry ID validation (InvalidBatchEntryId) and total batch size validation (BatchRequestTooLong)
  • Queue attribute range validation — validates VisibilityTimeout, DelaySeconds, ReceiveMessageWaitTimeSeconds, MaximumMessageSize, and MessageRetentionPeriod on both CreateQueue and SetQueueAttributes
  • Message size validation — rejects messages exceeding 1 MiB (1,048,576 bytes)
  • Unicode character validation — rejects messages with characters outside the AWS-allowed set
  • KMS attributesKmsMasterKeyId and KmsDataKeyReusePeriodSeconds are accepted and stored (no actual encryption)
  • FIFO queues.fifo suffix enforcement, MessageGroupId ordering, per-group locking (one inflight message per group), MessageDeduplicationId, content-based deduplication, sequence numbers, and FIFO-aware DLQ support
  • Queue tags

SNS Features

  • SNS-to-SQS fan-out — publish to a topic and messages are delivered to all confirmed SQS subscriptions
  • Filter policies — both MessageAttributes and MessageBody scope, supporting exact match, prefix, suffix, anything-but (including anything-but with suffix), numeric ranges, exists, null conditions, and $or top-level grouping. MessageBody scope supports nested key matching
  • Raw message delivery — configurable per subscription
  • Message size validation — rejects messages exceeding 256 KB (262,144 bytes)
  • Topic idempotency with conflict detectionCreateTopic returns the existing topic when called with the same name, attributes, and tags, but throws when attributes or tags differ
  • Subscription idempotency with conflict detectionSubscribe returns the existing subscription when the same (topic, protocol, endpoint) combination is used with matching attributes, but throws when attributes differ
  • Subscription attribute validationSetSubscriptionAttributes validates attribute names and rejects unknown or read-only attributes
  • Topic and subscription tags
  • FIFO topics.fifo suffix enforcement, MessageGroupId and MessageDeduplicationId passthrough to SQS subscriptions, content-based deduplication
  • Batch publish

S3 Features

  • Bucket management — CreateBucket (idempotent), DeleteBucket (rejects non-empty), HeadBucket, ListBuckets, ListObjects (V1 and V2)
  • Object operations — PutObject, GetObject, DeleteObject, HeadObject, CopyObject with ETag, Content-Type, and Last-Modified headers
  • Multipart uploads — CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload with correct multipart ETag calculation (MD5-of-part-digests-partCount), metadata preservation, and part overwrite support
  • ListObjects V2 — prefix filtering, delimiter-based virtual directories, MaxKeys, continuation tokens, StartAfter
  • CopyObject — same-bucket and cross-bucket copy via x-amz-copy-source header, with metadata preservation
  • User metadatax-amz-meta-* headers are stored and returned on GetObject and HeadObject
  • Bulk delete — DeleteObjects for batch key deletion with proper XML entity handling
  • Keys with slashes — full support for slash-delimited keys (e.g., path/to/file.txt)
  • Stream uploads — handles AWS chunked transfer encoding (Content-Encoding: aws-chunked) for stream bodies
  • Path-style and virtual-hosted-style — both S3 URL styles are supported (see below)

S3 URL styles

The AWS SDK sends S3 requests using virtual-hosted-style URLs by default (e.g., my-bucket.s3.localhost:4566). This requires *.localhost to resolve to 127.0.0.1. fauxqs provides helpers for this, plus a simple fallback.

Creates an HTTP request handler that resolves all hostnames to 127.0.0.1. Scoped to a single client instance — no side effects.

import { S3Client } from "@aws-sdk/client-s3";
import { createLocalhostHandler } from "fauxqs";

const s3 = new S3Client({
  endpoint: "http://s3.localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
  requestHandler: createLocalhostHandler(),
});

Option 2: interceptLocalhostDns() (global, for test suites)

Patches Node.js dns.lookup so that any hostname ending in .localhost resolves to 127.0.0.1. No client changes needed.

import { interceptLocalhostDns } from "fauxqs";

const restore = interceptLocalhostDns();

const s3 = new S3Client({
  endpoint: "http://s3.localhost:4566",
  region: "us-east-1",
  credentials: { accessKeyId: "test", secretAccessKey: "test" },
});

// When done (e.g., in afterAll):
restore();

The suffix is configurable: interceptLocalhostDns("myhost.test") matches *.myhost.test.

Tradeoffs: Affects all DNS lookups in the process. Best suited for test suites (beforeAll / afterAll).

Option 3: forcePathStyle (simplest fallback)

Forces the SDK to use path-style URLs (http://localhost:4566/my-bucket/key) instead of virtual-hosted-style. No DNS or handler changes needed, but affects how the SDK resolves S3 URLs at runtime.

const s3 = new S3Client({
  endpoint: "http://localhost:4566",
  forcePathStyle: true,
  // ...
});

Using with AWS CLI

fauxqs is wire-compatible with the standard AWS CLI. Point it at the fauxqs endpoint:

SQS

aws --endpoint-url http://localhost:4566 sqs create-queue --queue-name my-queue
aws --endpoint-url http://localhost:4566 sqs create-queue \
  --queue-name my-queue.fifo \
  --attributes FifoQueue=true,ContentBasedDeduplication=true
aws --endpoint-url http://localhost:4566 sqs send-message \
  --queue-url http://localhost:4566/000000000000/my-queue \
  --message-body "hello"

SNS

aws --endpoint-url http://localhost:4566 sns create-topic --name my-topic
aws --endpoint-url http://localhost:4566 sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:000000000000:my-topic \
  --protocol sqs \
  --notification-endpoint arn:aws:sqs:us-east-1:000000000000:my-queue

S3

aws --endpoint-url http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url http://localhost:4566 s3 cp file.txt s3://my-bucket/file.txt

If the AWS CLI uses virtual-hosted-style S3 URLs by default, configure path-style:

aws configure set default.s3.addressing_style path

Conventions

  • Account ID: 000000000000
  • Region: auto-detected from SDK Authorization header (defaults to us-east-1)
  • Queue URL format: http://sqs.{region}.{host}:{port}/000000000000/{queueName} (host defaults to localhost)
  • Queue ARN format: arn:aws:sqs:{region}:000000000000:{queueName}
  • Topic ARN format: arn:aws:sns:{region}:000000000000:{topicName}

Limitations

fauxqs is designed for development and testing. It does not support:

  • Non-SQS SNS delivery protocols (HTTP/S, Lambda, email, SMS)
  • Persistence across restarts
  • Authentication or authorization
  • Cross-region or cross-account operations

License

MIT