Skip to content

Next.js Setup

This guide explains how to integrate TApi with the Next.js App Router.

First, install the package:

Terminal window
npm install @farbenmeer/tapi
# or
pnpm add @farbenmeer/tapi

Create a central file to define your API structure. This acts as the source of truth for your routes.

src/api.ts
import { defineApi } from "@farbenmeer/tapi/server";
export const api = defineApi()
// We'll add routes here later
// .route("/hello", import("./routes/hello"))

Next.js App Router uses a catch-all route to handle dynamic API requests. Create a file at src/app/api/[...tapi]/route.ts.

This handler captures all requests to /api/* and routes them through TApi.

src/app/api/[...tapi]/route.ts
import { createRequestHandler } from "@farbenmeer/tapi/server";
import { api } from "@/api"; // Adjust import path as needed
const handler = createRequestHandler(api, {
basePath: "/api"
});
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const PATCH = handler;
export const DELETE = handler;

To consume your API from client components, create a type-safe fetch client.

src/client.ts
"use client";
import { createFetchClient } from "@farbenmeer/tapi/client";
import type { api } from "@/api";
// Assuming your API is mounted at /api
export const client = createFetchClient<typeof api>("/api");

Create a route handler file. For example, src/routes/hello.ts:

src/routes/hello.ts
import { defineHandler, TResponse } from "@farbenmeer/tapi/server";
export const GET = defineHandler({
authorize: () => true
}, async () => {
return TResponse.json({ message: "Hello from TApi!" });
});

And register it in your src/api.ts:

src/api.ts
import { defineApi } from "@farbenmeer/tapi/server";
export const api = defineApi()
.route("/hello", import("./routes/hello"));

Use the client in your interactive components:

src/app/page.tsx
"use client";
import { useState, useEffect } from "react";
import { client } from "@/client";
export default function ClientPage() {
const [msg, setMsg] = useState("");
useEffect(() => {
client.hello.get().then((data) => setMsg(data.message));
}, []);
if (!msg) return <div>Loading...</div>;
return <h1>{msg}</h1>;
}

For Server Components, you can skip the HTTP overhead by using createLocalClient. This calls your handlers directly.

Create a server-only client instance:

src/server-client.ts
import { createLocalClient } from "@farbenmeer/tapi/server";
import { api } from "@/api";
export const serverClient = createLocalClient(api);

Then use it in your Server Component:

src/app/page.tsx
import { serverClient } from "@/server-client";
export default async function ServerPage() {
const data = await serverClient.hello.get();
return <h1>{data.message}</h1>;
}

To enable tag-based revalidation across all cache layers, set up a shared PubSub instance and a revalidation endpoint.

First, create a shared module for the PubSub instance:

src/pubsub.ts
import { PubSub } from "@farbenmeer/tapi/server";
export const pubsub = new PubSub();

Update your route handler to use it:

src/app/api/[...tapi]/route.ts
import { createRequestHandler } from "@farbenmeer/tapi/server";
import { api } from "@/api";
import { pubsub } from "@/pubsub";
const handler = createRequestHandler(api, {
basePath: "/api",
cache: pubsub,
});
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const PATCH = handler;
export const DELETE = handler;

Then add a revalidation endpoint at src/app/api/revalidate/route.ts:

src/app/api/revalidate/route.ts
import { streamRevalidatedTags } from "@farbenmeer/tapi/server";
import { pubsub } from "@/pubsub";
export const GET = () => {
return streamRevalidatedTags({
cache: pubsub,
buildId: process.env.BUILD_ID!,
});
};

Set BUILD_ID to a value that changes on every deployment (e.g. a git commit hash) so the service worker cache is fresh after each deploy.

For setting up a service worker that connects to this endpoint, see the Service Worker guide. For details on how the cache layers interact, see Caching Strategies.