Advanced — Module 8

Power-Ups (App Functions)

Hands-on

Power-Ups: App Functions

Your frontend can query Grail directly, but it cannot call external APIs — CSP blocks it. App functions are serverless TypeScript functions that run on the backend, with no CSP restrictions. They're your proxy to the outside world.

Creating an App Function

npx dt-app function create --name get-status

This creates api/get-status.function.ts. Every .function.ts file in api/ is automatically exposed as an endpoint at /api/get-status.

// api/get-status.function.ts
export default async function (payload?: { region: string }) {
  const url = `https://api.example.com/status?region=${payload?.region ?? "all"}`;
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

Constraints:

  • 256MB RAM — enough for most API calls and data processing
  • 120-second timeout — long enough for slow external APIs
  • No persistent state — each invocation is independent
  • The function receives an optional payload parameter from the frontend

Calling from the Frontend

import { useAppFunction } from "@dynatrace-sdk/react-hooks";

interface StatusItem {
  region: string;
  status: string;
  latency: number;
}

export const StatusPanel = () => {
  const { data, isLoading, error } = useAppFunction<StatusItem[]>({
    name: "get-status",
    data: { region: "eu" },
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data?.map((item) => (
        <li key={item.region}>{item.region}: {item.status} ({item.latency}ms)</li>
      ))}
    </ul>
  );
};

The name matches the filename (minus .function.ts). The data object becomes the function's payload parameter.

Filtering in the Function

Process data server-side to reduce what the frontend receives:

// api/get-incidents.function.ts
export default async function (payload?: { active: boolean; severity: string }) {
  const res = await fetch("https://api.example.com/incidents");
  const incidents = await res.json();

  return incidents.filter((i: any) => {
    if (payload?.active !== undefined && i.active !== payload.active) return false;
    if (payload?.severity && i.severity !== payload.severity) return false;
    return true;
  });
}

Why Not Fetch Directly?

The frontend runs in a sandboxed iframe with CSP restrictions. External fetch() calls from React components will be blocked. App functions run on the server with no such restrictions — they can call any URL, parse any response, and return clean data to your frontend.

What's Next

In Module 9, we learn intents — the navigation system that lets your app link to other Dynatrace apps and vice versa.