• Tejaya's Blog
  • Posts
  • 🎉 Build a New Year Resolutions Tracker with Node.js and Redis

🎉 Build a New Year Resolutions Tracker with Node.js and Redis

Happy New Year, tejaya.tech readers! 🎉 The New Year is a time for fresh starts, and many of us set resolutions to make the coming year even better. But how do you keep track of those resolutions and stay on top of them?

In this tutorial, we’ll build a New Year Resolutions Tracker API using Node.js, Redis, and queues to handle high user traffic and ensure fast, reliable performance. By the end, you’ll have a scalable backend service that lets users add, view, and check off their resolutions efficiently.

What We’ll Build

Our API will support:

  1. Adding a resolution: Users can add their resolutions.

  2. Viewing all resolutions: Retrieve a user’s list of resolutions.

  3. Marking a resolution as complete: Users can update the status of a resolution.

  4. Caching and queuing: Use Redis to store resolutions and handle tasks efficiently.

Step 1: Setting Up the Project

Start by initializing a new Node.js project and installing the required dependencies.

mkdir resolutions-tracker
cd resolutions-tracker
npm init -y
npm install express redis bull uuid

Step 2: Designing the API

Let’s design the API endpoints for managing resolutions:

  • POST /resolutions: Add a new resolution.

  • GET /resolutions: View all resolutions.

  • PATCH /resolutions/🆔 Mark a resolution as complete.

Code Implementation

Here’s how we can implement the backend, index.js:

const express = require('express');
const redis = require('redis');
const { v4: uuidv4 } = require('uuid');
const Queue = require('bull');

const app = express();
app.use(express.json());

// Redis client
const redisClient = redis.createClient();

redisClient.on('error', (err) => console.error('Redis Error:', err));

// Queue for background tasks
const resolutionQueue = new Queue('resolutionQueue');

// Endpoint to add a new resolution
app.post('/resolutions', async (req, res) => {
  const { userId, resolution } = req.body;

  if (!userId || !resolution) {
    return res.status(400).send('Missing userId or resolution');
  }

  const resolutionId = uuidv4();
  const newResolution = {
    id: resolutionId,
    resolution,
    completed: false,
  };

  // Add resolution to Redis
  redisClient.rpush(`resolutions:${userId}`, JSON.stringify(newResolution), (err) => {
    if (err) return res.status(500).send('Error adding resolution');
    res.status(201).json({ message: 'Resolution added', resolution: newResolution });
  });
});

// Endpoint to view all resolutions
app.get('/resolutions/:userId', async (req, res) => {
  const { userId } = req.params;

  // Check if cached resolutions exist
  redisClient.lrange(`resolutions:${userId}`, 0, -1, (err, resolutions) => {
    if (err) return res.status(500).send('Error fetching resolutions');
    if (resolutions.length === 0) {
      return res.status(404).send('No resolutions found');
    }

    // Parse and return the resolutions
    const parsedResolutions = resolutions.map((r) => JSON.parse(r));
    res.json(parsedResolutions);
  });
});

// Endpoint to mark a resolution as complete
app.patch('/resolutions/:userId/:resolutionId', async (req, res) => {
  const { userId, resolutionId } = req.params;

  // Fetch all resolutions
  redisClient.lrange(`resolutions:${userId}`, 0, -1, (err, resolutions) => {
    if (err) return res.status(500).send('Error fetching resolutions');

    // Find and update the specific resolution
    const updatedResolutions = resolutions.map((r) => {
      const resolution = JSON.parse(r);
      if (resolution.id === resolutionId) {
        resolution.completed = true;
      }
      return JSON.stringify(resolution);
    });

    // Save the updated resolutions back to Redis
    redisClient.del(`resolutions:${userId}`, (err) => {
      if (err) return res.status(500).send('Error updating resolutions');

      redisClient.rpush(`resolutions:${userId}`, updatedResolutions, (err) => {
        if (err) return res.status(500).send('Error saving updated resolutions');
        res.json({ message: 'Resolution marked as complete' });
      });
    });
  });
});

// Process background queue tasks
resolutionQueue.process(async (job, done) => {
  console.log('Processing job:', job.data);
  // Here, you could send reminders or analytics
  done();
});

// Add background job example
app.post('/reminder', async (req, res) => {
  const { userId, message } = req.body;
  await resolutionQueue.add({ userId, message });
  res.json({ message: 'Reminder added to the queue' });
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Step 3: Testing the API

Here’s how you can test the API using tools like Postman or cURL:

  1. Add a resolution:

curl -X POST http://localhost:3000/resolutions \
-H "Content-Type: application/json" \
-d '{"userId": "user1", "resolution": "Learn Node.js"}'
  1. View all resolutions:

curl http://localhost:3000/resolutions/user1
  1. Mark a resolution as complete:

curl -X PATCH http://localhost:3000/resolutions/user1/<resolutionId>

Conclusion
New Year is a great time to explore fresh ideas and projects. 🎉 This resolutions tracker is not just a fun exercise but a foundation for building real-world scalable systems. You can extend this project by:

  • Adding a React frontend for a user-friendly interface.

  • Implementing notification systems to remind users of incomplete resolutions.

  • Analyzing user habits to improve goal-setting strategies.

Here’s to a productive and fulfilling New Year! 🚀

Reply

or to participate.