Ghetto SQS is a lightweight, self-hostable SQS-compatible queue server built with Fastify, Prisma, MongoDB, and Zod.
- Bun
>=1.3 - Node.js
>=22 - MongoDB database (local or remote)
bun installCopy .env.example into your runtime environment.
| Variable | Default | Purpose |
|---|---|---|
DATABASE_URL |
mongodb://localhost:27017/ghetto_sqs |
MongoDB connection string. |
HOST |
0.0.0.0 |
HTTP bind host. |
LOG_LEVEL |
info |
Server log level. |
MAX_VISIBILITY_EXTENSIONS |
20 |
Maximum number of visibility timeout extensions allowed per message. |
POISON_MESSAGE_RECEIVE_THRESHOLD |
50 |
Maximum receives for messages without DLQ policy before discard. |
PORT |
3000 |
HTTP bind port. |
QUEUE_MESSAGE_RETENTION_SECONDS |
345600 |
Retention window for queue messages (4 days). |
Example:
export DATABASE_URL="mongodb://localhost:27017/ghetto_sqs"
export HOST="0.0.0.0"
export LOG_LEVEL="info"
export PORT="3000"Default compose setup:
docker compose up -dThis uses:
docker-compose.yml.env
Development compose setup:
docker compose --env-file .env.dev -f docker-compose.dev.yml up -dThis uses:
docker-compose.dev.yml.env.dev
Development mode:
bun run devType checking:
bun run typecheckLint:
bun run lintTests:
bun run testCompile:
bun run buildStart server:
bun run startBefore release, run:
bun run lint
bun run typecheck
bun run test
bun run buildComplete API documentation is defined in openapi/openapi.yaml.
GET /healthGET /health/liveGET /health/readyPOST /v1/queues/:queueName/messagesGET /v1/queues/:queueName/messages/receiveDELETE /v1/queues/:queueName/messagesPOST /v1/queues/:queueName/messages/visibility
Queue operations are authless. No registration flow or signed headers are required.
ReceiveMessagereturns a freshreceiptHandleper delivery.- A message is in-flight until
visibilityExpiresAt. - When visibility expires, the old receipt handle is invalidated.
- Delete requires the active receipt handle for the current in-flight delivery.
- Deleting with a stale or random receipt handle returns
400 receipt_handle_invalid.
- FIFO queue names must end with
.fifo. - FIFO enqueue requires
messageGroupId. - Optional FIFO deduplication uses
messageDeduplicationId. - Deduplication window is 5 minutes per queue and deduplication ID.
DLQ policy is attached per message during enqueue:
deadLetterQueueNamemaxReceiveCount(optional override)
Rules:
deadLetterQueueNamemust differ from source queue name.- If
maxReceiveCountis omitted, server default max receive count is used. - When receive count reaches
maxReceiveCount, the message is moved to the DLQ before the next delivery attempt.
- Send a message:
curl -sS -X POST http://localhost:3000/v1/queues/jobs/messages \
-H 'content-type: application/json' \
-d '{"body":{"jobId":"job-123"},"delaySeconds":0}'- Receive a message:
curl -sS "http://localhost:3000/v1/queues/jobs/messages/receive?maxMessages=1&visibilityTimeoutSeconds=30"Example receive payload:
{
"messages": [
{
"approximateReceiveCount": 1,
"body": {
"jobId": "job-123"
},
"messageId": "67c211ef6c3e16a6c5fd72f1",
"queueName": "jobs",
"receiptHandle": "a88d51d0564ab5f6d264f250469dc64f24741e7e99fddc04da7f364e81f87397",
"visibilityExpiresAt": "2026-03-01T21:25:00.000Z"
}
]
}- Delete with receipt handle:
curl -sS -X DELETE http://localhost:3000/v1/queues/jobs/messages \
-H 'content-type: application/json' \
-d '{"receiptHandle":"<RECEIPT_HANDLE>"}'- Change visibility timeout with receipt handle:
curl -sS -X POST http://localhost:3000/v1/queues/jobs/messages/visibility \
-H 'content-type: application/json' \
-d '{"receiptHandle":"<RECEIPT_HANDLE>","visibilityTimeoutSeconds":60}'