TechnologyJune 14, 20253 min read

Best Monorepo Setup for Long-Running Background Jobs (Simple Guide)

Learn the simplest monorepo setup for running long background jobs using BullMQ and Node.js with PM2. Clean, scalable, and beginner-friendly.

Best Monorepo Setup for Long-Running Background Jobs (Simple Guide)

Best Monorepo Setup for Long-Running Background Jobs (Simple Guide)

Looking for a simple monorepo setup for long-running background jobs?


Here’s a beginner-friendly guide that actually works without hassle.


Introduction

Managing long-running background jobs in a monorepo can be tricky, especially when dealing with concurrency, job queues, and system resources. If you're struggling to find a clean solution that doesn't require hours of setup or complicated tooling, you're not alone.

In this article, we’ll walk through a simple, efficient monorepo structure that can run concurrent jobs for hours, handle database updates, and offer a lightweight dashboard—no fancy tools required.


Why Monorepos Make Sense for Background Jobs

A monorepo helps you keep multiple apps and packages in one place—ideal for tightly coupled services like:

  • API servers

  • Job queues

  • Workers for long-running tasks

  • Shared utilities and configs

This setup can make deployments faster and local development smoother.


Common Challenges in Long-Running Job Setups

Let’s talk about the pain points developers face:

  1. Jobs failing with "stalled" errors

  2. BullMQ Dashboard lagging or unresponsive

  3. Trigger.dev complexity for small teams

  4. Overkill infrastructure for simple use-cases

Despite using powerful machines (32 vCPU, 32 GB RAM), many find jobs failing without clear reasons. If you're seeing errors like job stalled more than allowable limit, you're likely hitting heartbeat or concurrency issues, not hardware limits.


The Ideal Simple Setup (Without Complex Tools)

Here's a clean and functional setup using only:

  • Node.js + Express

  • BullMQ

  • Redis

  • PM2 (for process management)

All inside a monorepo.


1. Folder Structure

my-monorepo/
├── apps/
│   ├── api/
│   └── worker/
├── packages/
│   ├── db/
│   └── utils/


2. Worker App with BullMQ

import { Worker } from 'bullmq';
import IORedis from 'ioredis';

const connection = new IORedis();

const worker = new Worker('long-jobs', async job => {
  await new Promise(resolve => setTimeout(resolve, 1000 * 60 * 60 * 2)); // 2hr task
}, {
  concurrency: 5,
  connection,
  lockDuration: 7200000
});

worker.on('completed', job => console.log(`Job ${job.id} completed`));
worker.on('failed', (job, err) => console.error(`Job ${job?.id} failed`, err));

3. PM2 Setup

module.exports = {
  apps: [
    { name: 'api', script: 'apps/api/index.js' },
    { name: 'worker', script: 'apps/worker/index.js' }
  ]
};

Use:

npx pm2 start ecosystem.config.js


4. Lightweight Dashboards


5. Prevent Stalled Jobs

  • Match lockDuration with job duration

  • Use async/await properly

  • Stable Redis connection

  • Avoid blocking code


✅ Summary

If you're tired of over-engineered solutions, a basic Node + BullMQ worker in a monorepo is all you need. Here's why:

  • Clean separation of concerns

  • Minimal tools, easy monitoring

  • Scalable with PM2

  • Flexible with Redis


🚀 Ready to Simplify?

Ditch the complexity and start small. Share this guide with your dev team or bookmark it for your next build.

Explore more tech tips at TrendPulseZone.com.