nuxt-simple-bullmq
v1.8.4
Published
Simple Nuxt module for background tasks using bullmq
Readme
Nuxt Simple BullMQ Module
Simple Nuxt 3 module using BullMQ and Redis for doing amazing things.
Features
- ⛰ Foo
- 🚠 Bar
- 🌲 Baz
Quick Setup
Install the module in your Nuxt application with one command:
npx nuxi module add nuxt-simple-bullmqNOTE: This is only tested with NodeJS 21 (not cloudflare/vercel etc) and Nuxt 4 with experimental features (see test workflows/files for more)
Add the config
// nuxt.config.ts
{
runtimeConfig: {
redis: {
url: 'redis://localhost:6379'
}
},
bullMq: {
// Where to load defined workers from (defineWorker files)
// this is optional, and will default to [ '<serverDir>/workers' ]
workerDirs: [ '/workers' ], // base path will be your "serverDir" e.g ./server/some-path
}
}or use NUXT_REDIS_URL in your $environment
That's it! You can now use BullMQ in your Nuxt app ✨
Usage
Workers
A worker lives in its own file and each worker is registered as a separate nitro plugin.
// ./server/workers/default,ts
export default defineWorker('default', {
async sendWelcomeEmail({job, logger, lockId}) {
logger.info(`Sending welcome email to ${job.data.email}`)
},
// magic catch-all event handler (for uncaught events):
catchAll({job, logger}) {
logger.debug(`Uncaught event: ${job.name}!`, job.data)
}
}, {
//optional: default //comment
concurrency: 1, //how many of each worker to run
});Jobs
Jobs are handled through callbacks, they can be in their own files, defined directly on the worker etc.
Note: There is no typing for dispatching jobs - yet :/ One solution can be to use a constant mapping e.g
const JobNames = {someKey: 'someValue'}
// ./server/jobs/sendWelcomeEmail.ts
export default defineJobHandler(({job, logger}) => {
logger.debug(job.name, job.data)
})Validated job handlers
// ./server/jobs/sendWelcomeEmail.ts
import {z} from 'zod';
export default defineValidatedJobHandler(
z.object({userId: z.string()}),
async ({data, job, logger}) => {
// data contains the validated payload from the schema
// data is also typed: {userId:string}
},
);Note: Validates input before processing the job
Delaying jobs
// ./server/jobs/onboarding/sendTipsAndTricks.ts
import {z} from 'zod'
import {DelayedError} from 'bullmq';
export default defineValidatedJobHandler(
z.object({userId: z.string().uuid()}),
async ({data: {userId}, job, logger, lockId}) => {
const DELAY_MS = 1_800_000 // 30 minutes
// do some checks...
if (!await userHasVerifierEmail() && !hasBeenMoreThan24HoursSinceSignUp()) {
logger.info('Preconditions not met, delaying job...')
await job.moveToDelayed(Date.now() + DELAY_MS, lockId)
throw new DelayedError();
}
logger.info(`Sending Tips & Tricks to user ${userId}`)
},
)Dispatching Jobs
// ./server/route/dispatch.ts
import {dispatchJob} from '#imports'
export default defineEventHandler(async event => {
await dispatchJob(
'sendWelcomeEmail',
{userId: 'abc'},
// Optional:
{queueName: 'default', attempts: 1, backoff: {strategy: 'exponential', } },
)
})Validated job dispatch
// ./server/route/typed-dispatch.ts
import {dispatchValidatedJob} from '#imports'
export default defineEventHandler(async event => {
await dispatchValidatedJob(
'sendWelcomeEmail',
z.object({userId: z.string()}),
{userId: 'abc'},
{queueName: 'default'},
)
})This will validate the input before emitting the job to redis
Additional options
You can pass the same options that are passed as the third argument when calling
emittodispatchJobanddispatchValidatedJobas well.
// ./server/route/name.ts
import {useQueue} from '#imports'
//e.g using H3 event handlers
export default defineEventHandler(async event => {
const queue = useQueue('default');
await queue.emit('sendWelcomeEmail', {userId: 'some-string'}, {
//Optionals (with their defaults)
queueName: 'default',
//optionals without defaults
deduplicationId: 'some-string', // can be anything, defaults to the event name.
ttl: 500, // when the deduplication should expire
delay: 500, // a delay to when this will run (good for notifications)
})
//validate schema first:
await queue.emit('sendWelcomeEmail', {userId: 'some-string'}, {
schema: z.object({userId: z.string()}), // optional
//... other options
})
})Roadmap
- [X] Add handlers
- [X] Worker per plugin
- [X] Validation dispatch/handler
- [ ] File based listeners (Laravel style with a "dispatch" method)
- [ ] Flow producers
- [ ] Different lib/platform (e.g Vercel/Cloudflare)
Contribution
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# run redis via docker (add -s to detach and continue using the terminal for other stuff)
docker compose -f ./playground/compose.yml up [-d]
# to stop docker stuff:
docker compose -f ./playground/compose.yml down
# Develop with the playground
npm run dev
# Build the playground
npm run dev:build
# Run ESLint
npm run lint
# Run Vitest
npm run test
npm run test:watch
# Release new version
npm run release