Queues
To process background jobs, decorate a method with @Queue (or wrap a function
in queue()) and send messages with getQueue().send(). Each consumer becomes
a queue plus a consumer function
— SQS plus a Lambda on AWS, an Azure Storage Queue plus a queue-triggered
function on Azure. The queue is managed by your cloud provider, so there is no
Redis instance to run or pay for.
This is infrastructure glue, not a job framework: you get delivery, retries, and
a dead-letter queue, not the scheduling and workflow features of something like
BullMQ. The same @Queue / queue() code works on both providers; FIFO is
the one capability that differs.
| Capability | AWS (SQS) | Azure (Storage Queue) |
|---|---|---|
| Standard queues | ✅ | ✅ |
getQueue().send() producer | ✅ | ✅ |
delaySeconds on send | ✅ | ✅ (message visibility delay) |
FIFO (fifo / .fifo, ordering, dedup) | ✅ | ❌ rejected at plan/deploy |
| Dead-letter queue | ✅ dlq (queue you name) | ⚠️ automatic ‹queue›-poison |
batchSize, visibilityTimeout, messageRetention | ✅ per-queue | ⚠️ ignored (host-wide or N/A) |
The Azure column is detailed in Deploying to Azure → Queues; everything else on this page applies to both.
Class style — @Queue
Decorate a method with @Queue:
import { Queue } from "@alzulejos/laranja-decorators";
export class Workers {
@Queue({ name: "emails", batchSize: 10 })
async sendEmail(body: unknown) {
// `body` is the JSON-parsed message
}
}
Function style — queue()
import { queue } from "@alzulejos/laranja-decorators";
export async function sendEmail(body: unknown) {
// …
}
queue({ name: "emails", batchSize: 10 }, sendEmail);
NestJS
@Queue works on a Nest provider with injected dependencies. As with
cron jobs, laranja resolves the consumer through your
DI container, so declare your module once with the
workers() marker
(export default workers(AppModule)) and deploy your compiled dist/ output.
Standalone queue() functions don't need it.
Options
| Option | Default | Description |
|---|---|---|
name | required | Queue name. A .fifo suffix marks a FIFO queue. |
batchSize | 10 | Max messages delivered to the consumer per invocation. |
fifo | false | Force a FIFO queue (or end name with .fifo). When set, laranja appends .fifo to name if you left it off. |
How messages are delivered
- Your handler is invoked once per message, with the message body already JSON-parsed.
- Partial-batch failures are enabled: if your handler throws for one message, only that message is retried — the rest of the batch is still acknowledged.
- Consumer memory/timeout come from
compute; on AWS the queue's visibility timeout is derived to stay ≥ the consumer timeout (override it per queue viaresources).
@Queue({ name: "orders" })
async processOrder(body: unknown) {
const order = body as { id: string };
if (!order.id) throw new Error("bad message"); // only THIS message is retried
// …
}
FIFO queues
AWS only. Azure Storage Queues have no ordering or deduplication, so a FIFO queue is rejected at
plan/deploytime there rather than silently downgraded.
End the name with .fifo (or set fifo: true) for ordered, exactly-once
processing. Content-based deduplication is enabled automatically:
@Queue({ name: "orders.fifo", fifo: true })
async processOrder(body: unknown) {
// …
}
AWS requires FIFO queue names to end in .fifo. If you set fifo: true but
leave the suffix off, laranja appends it for you — so { name: "orders", fifo: true }
deploys a queue named orders.fifo. The normalized name is what appears in
laranja plan and in the AWS console, so there's no surprise at deploy time.
Sending messages
Consuming is only half the loop — to produce a message, call
getQueue with the queue's
name and .send() a payload:
import { getQueue } from "@alzulejos/laranja-decorators";
app.post("/signup", async (req, res) => {
await getQueue("emails").send({ to: req.body.email, template: "welcome" });
res.sendStatus(202);
});
Objects are JSON-serialized for you (strings are sent as-is), so the consumer
receives them already parsed — getQueue("emails").send({ to }) on one end,
async sendEmail(body) on the other.
You can produce from anywhere in a deployed app — an HTTP route, a
cron job, or another queue's consumer fanning out. laranja wires
every function to send at deploy — on AWS it injects each queue's URL and grants
sqs:SendMessage; on Azure it uses the app's managed identity against the storage
account — so there's no client to configure, no URL to look up, and no IAM to wire.
It's a thin wrapper over one SendMessage call — laranja provisions the
infrastructure; it deliberately does not add a job framework (retries,
scheduling, and job state stay with the queue and your consumer).
FIFO and options
.send() takes a second options argument:
| Option | Applies to | Description |
|---|---|---|
groupId | FIFO (required) | MessageGroupId — messages with the same group are ordered. |
dedupId | FIFO | MessageDeduplicationId — only needed when content-based dedup is off. |
delaySeconds | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |
// FIFO queues require a groupId — the send throws without one.
await getQueue("orders.fifo").send(order, { groupId: order.customerId });
Prefer the raw SDK? The queue is a plain queue in your account — on AWS its URL is emitted as a stack output, so you can send with
@aws-sdk/client-sqsdirectly if you'd rather.
laranja