Skip to main content

User Registration Flow

import { UserBoost } from "@userboost/sdk";

// Initialize once at app startup
UserBoost.init({
  apiKey: process.env.USERBOOST_API_KEY,
  debug: process.env.NODE_ENV === "development",
});

// Registration endpoint
app.post("/api/register", async (req, res) => {
  const { email, password, name } = req.body;

  try {
    // Create user in your database
    const user = await User.create({ email, password, name });

    // Track registration event with user profile
    UserBoost.event("user_registered", {
      user: {
        id: user.id,
        email: user.email,
        name: user.name,
        traits: {
          plan: "free",
          created_at: user.createdAt.toISOString(),
        },
      },
      properties: {
        registration_source: "web",
      },
    });

    res.json({ success: true, userId: user.id });
  } catch (error) {
    // Track failed registration
    UserBoost.event("registration_failed", {
      user: {
        id: `visitor_${Date.now()}`,
      },
      properties: {
        email: email,
        error: error.message,
      },
    });

    res.status(400).json({ error: error.message });
  }
});

Onboarding Progress Tracking

// Track onboarding steps
app.post("/api/onboarding/:step", authenticateUser, async (req, res) => {
  const { step } = req.params;
  const { userId } = req.user;
  const stepData = req.body;

  // Update user progress in database
  await User.updateOnboardingStep(userId, step, stepData);

  // Track onboarding progress
  UserBoost.event("onboarding_step_completed", {
    user: {
      id: userId,
    },
    properties: {
      step: step,
      progress: stepData.progress || 0,
      time_spent: stepData.timeSpent || 0,
    },
  });

  // Check if onboarding is complete
  const user = await User.findById(userId);
  if (user.onboardingComplete) {
    UserBoost.event("onboarding_completed", {
      user: {
        id: userId,
      },
      properties: {
        completion_time: Date.now() - user.createdAt.getTime(),
        steps_completed: user.onboardingSteps.length,
      },
    });
  }

  res.json({ success: true });
});

Feature Usage Tracking

// Track feature usage with middleware
const trackFeatureUsage = (featureName) => {
  return (req, res, next) => {
    const startTime = Date.now();

    // Continue with request
    next();

    // Track after response (don't block the response)
    res.on("finish", () => {
      if (req.user && res.statusCode === 200) {
        UserBoost.event("feature_used", {
          user: {
            id: req.user.id,
          },
          properties: {
            feature: featureName,
            duration: Date.now() - startTime,
            endpoint: req.path,
            method: req.method,
          },
        });
      }
    });
  };
};

// Apply to specific routes
app.get(
  "/api/dashboard",
  authenticateUser,
  trackFeatureUsage("dashboard"),
  (req, res) => {
    // Your dashboard logic
    res.json(dashboardData);
  }
);

app.post(
  "/api/projects",
  authenticateUser,
  trackFeatureUsage("create_project"),
  (req, res) => {
    // Your project creation logic
    res.json({ success: true });
  }
);

Subscription Events

// Track subscription changes
app.post("/api/upgrade", authenticateUser, async (req, res) => {
  const { userId } = req.user;
  const { plan, billingCycle } = req.body;

  try {
    // Process upgrade in your billing system
    const subscription = await Stripe.createSubscription({
      customer: userId,
      plan: plan,
      billing_cycle: billingCycle,
    });

    // Update user in database
    await User.updatePlan(userId, plan);

    // Track upgrade event with updated user traits
    UserBoost.event("user_upgraded", {
      user: {
        id: userId,
        traits: {
          plan: plan,
          billing_cycle: billingCycle,
          upgraded_at: new Date().toISOString(),
        },
      },
      properties: {
        from_plan: req.user.plan,
        to_plan: plan,
        billing_cycle: billingCycle,
        upgrade_value: getPlanValue(plan) - getPlanValue(req.user.plan),
      },
    });

    res.json({ success: true, subscription });
  } catch (error) {
    UserBoost.event("upgrade_failed", {
      user: {
        id: userId,
      },
      properties: {
        plan: plan,
        error: error.message,
      },
    });

    res.status(400).json({ error: error.message });
  }
});

Batch Event Processing

// Batch events for high-volume applications
class UserBoostBatcher {
  constructor() {
    this.events = [];
    this.batchSize = 50;
    this.flushInterval = 5000; // 5 seconds

    // Auto-flush periodically
    setInterval(() => this.flush(), this.flushInterval);
  }

  add(eventName, userId, properties) {
    this.events.push({
      name: eventName,
      userId: userId,
      properties: properties,
      timestamp: new Date().toISOString(),
    });

    // Flush if batch is full
    if (this.events.length >= this.batchSize) {
      this.flush();
    }
  }

  async flush() {
    if (this.events.length === 0) return;

    const batch = this.events.splice(0);

    try {
      // Send batch to UserBoost
      await Promise.all(
        batch.map((event) =>
          UserBoost.event(event.name, {
            user: {
              id: event.userId,
            },
            properties: event.properties,
          })
        )
      );
    } catch (error) {
      console.error("Batch flush failed:", error);
      // Could implement retry logic here
    }
  }
}

// Usage
const batcher = new UserBoostBatcher();

// Add events to batch instead of sending immediately
app.post("/api/action", authenticateUser, (req, res) => {
  batcher.add("user_action", req.user.id, {
    action: req.body.action,
    context: req.body.context,
  });

  res.json({ success: true });
});

Error Handling and Resilience

// Graceful error handling wrapper
const safeTrack = async (eventName, userId, properties) => {
  try {
    await UserBoost.event(eventName, {
      user: { id: userId },
      properties: properties,
    });
  } catch (error) {
    // Log error but don't break application flow
    console.error("UserBoost tracking failed:", error);

    // Optional: Store failed events for retry
    await storeFailedEvent(eventName, userId, properties);
  }
};

// Retry failed events
const retryFailedEvents = async () => {
  const failedEvents = await getFailedEvents();

  for (const event of failedEvents) {
    try {
      await UserBoost.event(event.name, {
        user: {
          id: event.userId,
        },
        properties: event.properties,
      });

      // Remove from failed events if successful
      await removeFailedEvent(event.id);
    } catch (error) {
      // Keep in failed events for next retry
      console.error("Retry failed for event:", event.id);
    }
  }
};

// Run retry every 5 minutes
setInterval(retryFailedEvents, 5 * 60 * 1000);

Testing Integration

// Mock UserBoost for testing
const mockUserBoost = {
  init: jest.fn(),
  event: jest.fn().mockResolvedValue({ success: true }),
  identify: jest.fn().mockResolvedValue({ success: true }),
};

// Use in tests
jest.mock("@userboost/sdk", () => mockUserBoost);

// Test registration tracking
test("tracks user registration", async () => {
  const response = await request(app)
    .post("/api/register")
    .send({ email: "test@example.com", password: "password" });

  expect(response.status).toBe(200);
  expect(mockUserBoost.event).toHaveBeenCalledWith("user_registered", {
    user: {
      id: expect.any(String),
    },
    properties: {
      email: "test@example.com",
      registration_source: "web",
    },
  });
});

Best Practices

  1. Initialize once at application startup
  2. Don’t block requests - track events asynchronously
  3. Handle failures gracefully - never break user flow for tracking
  4. Batch high-volume events to reduce API calls
  5. Use meaningful event names and consistent properties
  6. Test your tracking with mock implementations
  7. Monitor tracking health with logs and metrics

Performance Tips

  • Use background workers for non-critical events
  • Implement connection pooling for high-volume apps
  • Cache user data to avoid repeated identify calls
  • Use environment variables for configuration
  • Monitor memory usage when batching events
I