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.

CapabilityAWS (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 queuedlq (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

OptionDefaultDescription
namerequiredQueue name. A .fifo suffix marks a FIFO queue.
batchSize10Max messages delivered to the consumer per invocation.
fifofalseForce 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 via resources).
@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/deploy time 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:

OptionApplies toDescription
groupIdFIFO (required)MessageGroupId — messages with the same group are ordered.
dedupIdFIFOMessageDeduplicationId — only needed when content-based dedup is off.
delaySecondsStandardDelay (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-sqs directly if you'd rather.