local-sqs-trigger
v1.0.2
Published
Trigger AWS Lambda SQS handlers locally for development and testing. Simulate SQS events using LocalStack and let your handler code runs unchanged and deploys to AWS as-is.
Maintainers
Readme
local-sqs-trigger
npm library to simulate AWS Lambda SQS triggers locally using LocalStack. Your handler code runs locally unchanged and deploys to AWS as-is.
The problem
When you write a Lambda function triggered by SQS, the handler looks like this:
export const handler: SQSHandler = async (event) => {
for (const record of event.Records) {
await processOrder(JSON.parse(record.body))
}
}AWS manages the polling, batching, and message lifecycle for you. But locally, there is no equivalent. To test this code without deploying, you either write your own polling loop that you don't need in production — mixing dev scaffolding into your application code — or you deploy every change to AWS to test it, which is slow and expensive.
The solution
local-sqs-trigger is a thin runner that sits outside your application code. You write a single config file that maps queues to handler files, and the runner handles the polling, batching, visibility timeout, and message lifecycle — exactly as Lambda does in production.
Your handler code stays clean. No polling logic, no dev-only code paths. The same file deploys to Lambda unchanged.
Features
- Same event shape as real Lambda — your handler receives the exact same
SQSEventit would get in AWS, no changes needed when you deploy - Partial batch failure — return
SQSBatchResponseto fail individual messages while succeeding others - TypeScript-first — full type exports,
.tshandler files supported at runtime viatsx - Config-driven — one file maps every queue to its handler
- FIFO queue support —
MessageGroupIdand deduplication attributes preserved - Graceful shutdown —
SIGINT/SIGTERMwaits for the current batch to finish - Works against real AWS too — omit
endpointto point at real SQS
Requirements
- Node.js >= 18
- LocalStack (for local development)
Installation
npm install local-sqs-triggerQuick Start
1. Start LocalStack
Docker (typical for local dev):
docker run --rm -p 4566:4566 -e SERVICES=sqs localstack/localstackDocker Compose in your own repo (optional): create a docker-compose.yml next to your app and run docker compose up -d. Example:
services:
localstack:
image: localstack/localstack:latest
ports:
- "4566:4566"
environment:
- SERVICES=sqsUse http://localhost:4566 as endpoint in the config below. If you use another host or port, adjust accordingly.
2. Create a config file
// sqs-triggers.config.ts
import { defineConfig } from 'local-sqs-trigger'
export default defineConfig({
endpoint: 'http://localhost:4566',
region: 'us-east-1',
credentials: { accessKeyId: 'test', secretAccessKey: 'test' },
queues: [
{
name: 'order-processing-queue',
handler: './handlers/orderHandler',
batchSize: 10,
createIfNotExists: true,
},
],
})3. Write a handler
// handlers/orderHandler.ts
import type { SQSHandler } from 'local-sqs-trigger'
export const handler: SQSHandler = async (event) => {
for (const record of event.Records) {
const order = JSON.parse(record.body)
console.log('Processing order:', order.id)
}
}4. Start the trigger
npx local-sqs-trigger start
# or specify a custom config path
npx local-sqs-trigger start --config ./config/sqs.config.tsConfig Reference
interface TriggerConfig {
/** LocalStack or custom SQS endpoint. Omit for real AWS. */
endpoint?: string
/** AWS region. Default: 'us-east-1' */
region?: string
/** AWS credentials. Defaults to the standard credential chain. */
credentials?: {
accessKeyId: string
secretAccessKey: string
sessionToken?: string
}
/** One entry per queue. */
queues: QueueConfig[]
/** Log verbosity. Default: 'info' */
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent'
}
interface QueueConfig {
/** SQS queue name (not a URL or ARN). */
name: string
/**
* Path to the handler file, relative to the config file.
* Supports .ts, .js, .mjs extensions. No extension required.
*/
handler: string
/** Messages per poll (1–10). Default: 10 */
batchSize?: number
/** Visibility timeout in seconds while the handler runs. */
visibilityTimeout?: number
/**
* Auto-create the queue if it doesn't exist.
* Intended for local dev only. Default: false (matches AWS behaviour).
*/
createIfNotExists?: boolean
/** Extra queue attributes passed to CreateQueue (when createIfNotExists is true). */
queueAttributes?: Record<string, string>
}Handler Signature
Handlers match the real AWS Lambda SQS handler contract exactly:
import type { SQSHandler, SQSEvent, SQSBatchResponse } from 'local-sqs-trigger'
// Simple handler — all messages succeed or all fail together
export const handler: SQSHandler = async (event: SQSEvent) => {
for (const record of event.Records) {
await processMessage(JSON.parse(record.body))
}
}
// Partial batch failure — fail individual messages independently
export const handler: SQSHandler = async (event): Promise<SQSBatchResponse> => {
const failures: string[] = []
for (const record of event.Records) {
try {
await processMessage(JSON.parse(record.body))
} catch {
failures.push(record.messageId)
}
}
return { batchItemFailures: failures.map((id) => ({ itemIdentifier: id })) }
}When a handler throws, all messages in the batch are released back to the queue immediately (visibility timeout set to 0), matching real Lambda behaviour.
When a handler returns an SQSBatchResponse, only the failed message IDs are released; the rest are deleted.
Programmatic API
import { startTriggers, defineConfig } from 'local-sqs-trigger'
const instance = await startTriggers(defineConfig({
endpoint: 'http://localhost:4566',
queues: [{ name: 'my-queue', handler: './handler' }],
}))
// Graceful shutdown
process.once('SIGINT', () => instance.stop())
await instance.stoppedFIFO Queues
Use a queue name ending in .fifo. The MessageGroupId and MessageDeduplicationId are preserved in the SQSRecord attributes, matching the real Lambda event shape.
export default defineConfig({
queues: [
{
name: 'orders.fifo',
handler: './handlers/orders',
createIfNotExists: true,
queueAttributes: {
FifoQueue: 'true',
ContentBasedDeduplication: 'true',
},
},
],
})Config File Discovery
The CLI searches for config files in this order:
sqs-triggers.config.tssqs-triggers.config.jssqs-triggers.config.mjssqs-triggers.config.cjssqs-triggers.config.json
Pass --config <path> to use a different location.
Contributing
To work on the library itself, clone the GitHub repository. It includes a docker-compose.yml for LocalStack used in development and CI-style integration tests.
git clone https://github.com/BhanukaRC/local-sqs-trigger.git
cd local-sqs-trigger
npm install
npm test
docker compose up -d # LocalStack on :4566
npm run test:integrationLicense
MIT
