Next.js Setup
This guide explains how to integrate Toapi with the Next.js App Router.
Installation
Section titled “Installation”npm install @toapi/server @toapi/client zod# orpnpm add @toapi/server @toapi/client zod1. Define your API
Section titled “1. Define your API”Create a central file to define your API structure. This acts as the source of truth for your routes.
import { defineApi } from "@toapi/server";
export const api = defineApi();// We'll add routes here later// .route("/hello", import("./routes/hello"))2. Create the route handler
Section titled “2. Create the route handler”The 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 Toapi.
import { createRequestHandler } from "@toapi/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;3. Create the client
Section titled “3. Create the client”To consume your API from client components, create a type-safe fetch client.
"use client";
import { createFetchClient } from "@toapi/client";import type { api } from "@/api";
// Assuming your API is mounted at /apiexport const client = createFetchClient<typeof api.routes>("/api");4. Add a route
Section titled “4. Add a route”Create a route handler file. For example, src/routes/hello.ts:
import { defineHandler, TResponse } from "@toapi/server";
export const GET = defineHandler({ authorize: () => true }, async () => { return TResponse.json({ message: "Hello from Toapi!" });});And register it in src/api.ts:
import { defineApi } from "@toapi/server";
export const api = defineApi().route("/hello", import("./routes/hello"));5. Usage in components
Section titled “5. Usage in components”Client components
Section titled “Client components”Use the client in your interactive components:
"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>;}Server components
Section titled “Server components”For Server Components, skip the HTTP overhead by using createLocalClient. This calls your handlers directly.
Create a server-only client instance:
import { createLocalClient } from "@toapi/server";import { api } from "@/api";
export const serverClient = createLocalClient(api);Then use it in your Server Component:
import { serverClient } from "@/server-client";
export default async function ServerPage() { const data = await serverClient.hello.get();
return <h1>{data.message}</h1>;}6. Revalidation stream
Section titled “6. Revalidation stream”To enable tag-based revalidation across all cache layers, expose the invalidation stream. defineApi automatically creates a PubSub instance, so no extra setup is needed for single-host deployments.
The catch-all handler already serves the stream at /api/__tapi/invalidations. If you prefer a dedicated route, add one at src/app/api/revalidate/route.ts:
import { streamRevalidatedTags } from "@toapi/server";import { api } from "@/api";
export const GET = () => { return streamRevalidatedTags({ cache: api.cache, });};For setting up a service worker that connects to this endpoint, see the @toapi/worker package. For details on how the cache layers interact, see Caching Strategies.