Tutorial Metered Monitoring

Building a Metered Usage Monitoring and Alerting Dashboard with Next.js and Schematic

imagejas
Jasdeep Garcha
·
12/05/2024

We often get asked by our users if we can provide notifications for the customers that are approaching a limit in their products. Visibility into that type of information is incredibly useful, particularly for startups trying to identify their most active users or those ready for an upsell conversation.

However, it’s often challenging data both to get and to visualize in a user-friendly way that folks can act on. That’s because this type of data is often locked in application databases that need to be queried carefully & manually. Fortunately, Schematic supports metered features, and tracks data about limits and utilization on a per feature and per customer basis, which can be retrieved via our API.

In this tutorial, we'll build a small app that monitors feature usage across a customer base and triggers webhook notifications when customers are reaching usage limits. We'll use Next.js, Schematic's API to retrieve allocation and usage data for individual features, and webhook notifications for alerting.

In the future, Schematic will natively support triggers for limits, but we can easily implement something similar with data from Schematic today and customize to your specific use case.

You can find all the code associated with this tutorial here.

Image

What We're Building

We'll create a dashboard that:

  • Displays feature usage across all companies using your product

  • Automatically polls for updates every 5 minutes

  • Triggers webhook notifications at 80%, 90%, and 100% usage thresholds

  • Provides a log of all threshold notifications

Prerequisites

Before starting, you'll need:

  • Node.js 18 or higher installed

  • A Schematic account that’s instrumented with metered features

  • A webhook endpoint for receiving notifications (optional)

Initial Setup

Create a new Next.js project:

1npx create-next-app@latest usage-monitor --typescript --tailwind --eslint  
2cd /usage-monitor

Install dependencies:

1npx shadcn@latest init
2npx shadcn@latest add card alert input button tabs
3npm install @schematichq/schematic-typescript-node

Build the Backend

Create an API route to handle feature usage data from Schematic:

1// app/api/feature-usage/route.ts
2import { NextRequest, NextResponse } from "next/server";
3import { SchematicClient } from "@schematichq/schematic-typescript-node";
4
5const DEFAULT_PAGE_SIZE: number = 100;
6
7export async function GET(request: NextRequest) {
8  const apiKey = process.env.SCHEMATIC_SECRET_KEY;
9  if (!apiKey) {
10    return NextResponse.json({ message: "No Schematic key" }, { status: 400 });
11  }
12
13  const searchParams = request.nextUrl.searchParams;
14  const featureId = searchParams.get('featureId');
15  const offset = parseInt(searchParams.get('offset') || '0');
16  const limit = parseInt(searchParams.get('limit') || String(DEFAULT_PAGE_SIZE));
17
18  try {
19    const schematicClient = new SchematicClient({ apiKey });
20    const response = await schematicClient.entitlements.listFeatureCompanies({
21      featureId,
22      limit,
23      offset,
24    });
25
26    return NextResponse.json({
27      data: response.data,
28      pagination: {
29        offset,
30        limit,
31        total: response.data.length,
32        hasMore: response.data.length === limit
33      }
34    });
35  } catch (error) {
36    return NextResponse.json(
37      { message: "Failed to fetch feature usage data" },
38      { status: 500 }
39    );
40  }
41}

Create a notification endpoint that triggers webhooks at 80%, 90%, and 100% of a given limit for a feature:

1// app/api/usage-notifications/route.ts
2import { NextRequest, NextResponse } from "next/server";
3import { SchematicClient } from "@schematichq/schematic-typescript-node";
4
5const THRESHOLDS = [80, 90, 100];
6
7export async function POST(request: NextRequest) {
8  const apiKey = process.env.SCHEMATIC_SECRET_KEY;
9  if (!apiKey) {
10    return NextResponse.json({ message: "No Schematic key" }, { status: 400 });
11  }
12
13  const { featureId } = await request.json();
14  const schematicClient = new SchematicClient({ apiKey });
15
16  try {
17    const response = await schematicClient.entitlements.listFeatureCompanies({
18      featureId,
19    });
20
21   const notifications = [];
22
23    for (const company of response.data) {
24      const usagePercentage = (company.usage / (company.allocation || 1)) * 100;
25
26      for (const threshold of THRESHOLDS) {
27        if (usagePercentage >= threshold) {
28          notifications.push({
29            companyId: company.company.id,
30            companyName: company.company.name,
31            threshold,
32            usage: company.usage,
33            allocation: company.allocation,
34            timestamp: new Date().toISOString()
35          });
36
37          if (process.env.WEBHOOK_URL) {
38            await fetch(process.env.WEBHOOK_URL, {
39              method: 'POST',
40              headers: { 'Content-Type': 'application/json' },
41              body: JSON.stringify({
42                companyId: company.company.id,
43                threshold,
44                usage: company.usage,
45                allocation: company.allocation
46              })
47            });
48          }
49        }
50      }
51    }
52
53    return NextResponse.json({ notifications });
54  } catch (error) {
55    return NextResponse.json(
56      { message: "Failed to process notifications" },
57      { status: 500 }
58    );
59  }
60}

Build the Dashboard

1. Set Up Types

Create types for the Schematic API response:

1export interface Company {
2  company: {
3    id: string;
4    name: string;
5    logo_url: string;
6    plan?: {
7      name: string;
8    };
9  };
10  period: string;
11  usage: number;
12  allocation: number | null;
13}
14
15export interface PaginationState {
16  offset: number;
17  limit: number;
18  total?: number;
19  hasMore: boolean;
20}
21
22export interface ApiResponse {
23  data: Company[];
24  pagination: PaginationState;
25}

2. Create the Usage List Component

This dashboard component fetches and displays usage data, sorted by utilization percentage:

1export interface CompanyUsageListProps {
2  data: ApiResponse | null;
3  addWebhookLog: (log: WebhookLog) => void;
4}
5
6export const CompanyUsageList = ({ 
7  data, 
8  addWebhookLog
9}: CompanyUsageListProps) => {
10  const companies = data?.data || [];
11
12  // Sort companies by utilization percentage
13  const sortedCompanies = [...companies].sort((a, b) => {
14    const percentageA = calculateUsagePercentage(a.usage, a.allocation);
15    const percentageB = calculateUsagePercentage(b.usage, b.allocation);
16    return percentageB - percentageA;
17  });
18
19  return (
20    <div className="space-y-4">
21      {sortedCompanies.map((company) => {
22        const usagePercentage = calculateUsagePercentage(company.usage, company.allocation);
23
24        return (
25            // ... company usage list layout ...
26  );
27};

3. Implement Usage Threshold Monitoring

Set up threshold checking and webhook notifications:

1useEffect(() => {
2    const checkUsageAndNotifications = async () => {
3      await fetchData(featureId);
4      // Call our notifications endpoint
5      try {
6        const response = await fetch('/api/usage-notifications', {
7          method: 'POST',
8          headers: {
9            'Content-Type': 'application/json',
10          },
11          body: JSON.stringify({ featureId })
12        });
13
14    return () => clearInterval(interval);
15  }, [featureId]);

4. Set Up Auto-Polling

Implement automatic refresh of data from Schematic:

1const POLLING_INTERVAL: number = 5 * 60 * 1000; // 5 minutes
2
3export const CompanyUsageApp = () => {
4  // ... state setup ...
5
6      const setupPolling = useCallback(() => {
7	    if (pollInterval.current) {
8	      clearInterval(pollInterval.current);
9	    }
10
11          pollInterval.current = setInterval(() => {
12	      fetchData(featureId);
13	    }, POLLING_INTERVAL);
14
15            return () => {
16	      if (pollInterval.current) {
17	        clearInterval(pollInterval.current);
18	      }
19	    };
20	  }, [featureId]);
21
22  // ... rest of component
23};

5. Build the Frontend

Pull together the full dashboard component. It will include:

  • An input field for the feature ID

  • List of companies with utilization and allocation

  • Pagination support

The code for that is here.

Next Steps

There are several next steps you could take to improve upon this, including:

  • Adding email notifications that go directly to account owners

  • Implementing custom threshold settings

  • Support for monitoring every metered feature at once, rather than one at a time

  • Adding historical usage tracking for trend analysis

  • Adding database persistence for notification history