Blog
API layer in Umami codebase - Part 1.2

API layer in Umami codebase - Part 1.2

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

In part 1.1, we reviewed the API layer in websites route. In this article, we review the /links route

You will find the links page in umami/src/app/(main)/links/page.tsx. Again, in the API layer part 1.1, we reviewed the websites page, let’s check if there is a common pattern implemented in fetching the list of links as well.

For that, we will review:

  1. LinksDataTable.tsx

  2. useLinksQuery

LinksDataTable.tsx

You will find the following code in links/LinksDataTable.tsx

import { useLinksQuery, useNavigation } from '@/components/hooks';
import { LinksTable } from './LinksTable';
import { DataGrid } from '@/components/common/DataGrid';

export function LinksDataTable() {
  const { teamId } = useNavigation();
  const query = useLinksQuery({ teamId });

  return (
    <DataGrid query={query} allowSearch={true} autoFocus={false} allowPaging={true}>
      {({ data }) => <LinksTable data={data} />}
    </DataGrid>
  );
}

I can see a pattern here, both in the /websites and the /links, Tanstack query is used to fetch the data:

// Snippet from /websites page
const queryResult = useUserWebsitesQuery({ userId: userId || user?.id, teamId });
// Snippet from /links page
const query = useLinksQuery({ teamId });

One difference is that variable is named differently. Do you see it? queryResult and query.

and this data is passed down as a prop to LinksTable component.

useLinksQuery

useLinksQuery is defined as shown below

import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified';
import { ReactQueryOptions } from '@/lib/types';

export function useLinksQuery({ teamId }: { teamId?: string }, options?: ReactQueryOptions) {
  const { modified } = useModified('links');
  const { get } = useApi();

  return usePagedQuery({
    queryKey: ['links', { teamId, modified }],
    queryFn: pageParams => {
      return get(teamId ? `/teams/${teamId}/links` : '/links', pageParams);
    },
    ...options,
  });

I see useApi is used again, just like in websites hook and you can learn more about what useApi hook is for in API layer part 1.0.

Conclusion

I can see a pattern here, both websites and links fetch the data using Tanstack query hooks and passed down the data as a prop to a table component to render. I also found that fetcher is reused, defined in the useApi.

It is important to stay consistent to reduce technical debt and write manageable code, especially when you are dealing with large codebases.

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)/links/LinksDataTable.tsx#L5

  2. https://github.com/umami-software/umami/blob/master/src/app/(main)/links/LinksTable.tsx#L9