Blog
API layer in Umami codebase - Part 1.3

API layer in Umami codebase - Part 1.3

Inspired by BulletProof React, I applied its codebase architecture concepts to the Umami codebase.

This article focuses only on the API layer in Umami codebase.

Prerequisites

  1. API layer in Umami codebase — Part 1.0

  2. API layer in Umami codebase — Part 1.1

  3. API layer in Umami codebase — Part 1.2

In the part 1.1, 1.2, we reviewed the API layer in websites and links page.

In this article, we review the website detailed overview page. There is a lot of sections on the overview page in the website detail page, we will focus on:

  1. Stats

2. Page views

Where do you find these components? In websites/[websiteId]/WebsitePage.tsx, you will find the following code:

'use client';
import { Column } from '@umami/react-zen';
import { Panel } from '@/components/common/Panel';
import { WebsiteChart } from './WebsiteChart';
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
import { WebsitePanels } from './WebsitePanels';
import { WebsiteControls } from './WebsiteControls';
import { ExpandedViewModal } from '@/app/(main)/websites/[websiteId]/ExpandedViewModal';

export function WebsitePage({ websiteId }: { websiteId: string }) {
  return (
    <Column gap>
      <WebsiteControls websiteId={websiteId} />
      <WebsiteMetricsBar websiteId={websiteId} showChange={true} />
      <Panel minHeight="520px">
        <WebsiteChart websiteId={websiteId} />
      </Panel>
      <WebsitePanels websiteId={websiteId} />
      <ExpandedViewModal websiteId={websiteId} />
    </Column>
  );
}

WebsiteMetricsBar component shows the stats and the WebsiteChart component shows the page views.

Stats

To understand Stats, we need to review how the WebsiteMetricsBar component fetches the stats.

In WebsiteMetricsBar.tsx, you will find the below code at L17

const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId);

If you remember, in the links, websites pages, we see direct variable assignment to query or queryResult but here you got have data, isLoading, isFetching and error de-structured.

useWebsiteStatsQuery.ts

You will find the following code in useWebsiteStatsQuery.ts file:

import { UseQueryOptions } from '@tanstack/react-query';
import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '@/components/hooks/useDateParameters';

export interface WebsiteStatsData {
  pageviews: number;
  visitors: number;
  visits: number;
  bounces: number;
  totaltime: number;
  comparison: {
    pageviews: number;
    visitors: number;
    visits: number;
    bounces: number;
    totaltime: number;
  };
}

export function useWebsiteStatsQuery(
  websiteId: string,
  options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
) {
  const { get, useQuery } = useApi();
  const { startAt, endAt, unit, timezone } = useDateParameters();
  const filters = useFilterParameters();

  return useQuery<WebsiteStatsData>({
    queryKey: ['websites:stats', { websiteId, startAt, endAt, unit, timezone, ...filters }],
    queryFn: () =>
      get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, ...filters }),
    enabled: !!websiteId,
    ...options,
  });
}

These query hooks follow same pattern across the websites, links and this one.

Page Views

In the WebsiteChart.ts, you will find the following code at L17

const { data, isLoading, isFetching, error } = useWebsitePageviewsQuery({
  websiteId,
  compare: compareMode ? dateCompare?.compare : undefined,
});
const { pageviews, sessions, compare } = (data || {}) as any;

Here also the query results is de-structured and you get pageViews from data object as shown above.

useWebsitePageviewsQuery

You will find the following code in useWebsitePageviewsQuery.

import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';

export interface WebsitePageviewsData {
  pageviews: { x: string; y: number }[];
  sessions: { x: string; y: number }[];
}

export function useWebsitePageviewsQuery(
  { websiteId, compare }: { websiteId: string; compare?: string },
  options?: ReactQueryOptions<WebsitePageviewsData>,
) {
  const { get, useQuery } = useApi();
  const { startAt, endAt, unit, timezone } = useDateParameters();
  const queryParams = useFilterParameters();

  return useQuery<WebsitePageviewsData>({
    queryKey: [
      'websites:pageviews',
      { websiteId, compare, startAt, endAt, unit, timezone, ...queryParams },
    ],
    queryFn: () =>
      get(`/websites/${websiteId}/pageviews`, {
        compare,
        startAt,
        endAt,
        unit,
        timezone,
        ...queryParams,
      }),
    enabled: !!websiteId,
    ...options,
  });
}

Conclusion

We reviewed two components in the overview page and found that these components fetch their required data instead of doing it in the base component. 

You put things where they belong and they run multiple queries to get this information instead of a single query that gives you the entire overview page data. Something to keep in mind I guesss, when you are dealing with lots of data, especially when there’s graphs involved.

We also saw the query result is de-structured.

About me:

Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.

Email: ramu.narasinga@gmail.com

I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com

References:

  1. https://github.com/umami-software/umami/blob/master/src/app/(main)/websites/%5BwebsiteId%5D/WebsiteMetricsBar.tsx#L8

  2. https://github.com/umami-software/umami/blob/master/src/components/hooks/queries/useWebsiteStatsQuery.ts#L21

  3. https://github.com/umami-software/umami/blob/master/src/app/(main)/websites/%5BwebsiteId%5D/WebsiteChart.tsx#L7

  4. https://github.com/umami-software/umami/blob/master/src/components/hooks/queries/useWebsitePageviewsQuery.ts#L11