Data & Route Handlers
Serve deployments from app/api, fetch them in a page and post new ones.
In this chapter
- A route handler
- Fetching in a page
- Loading and error states
- Streaming
Acme Deploys needs real data. Route handlers live next to your pages in app/, use the Web Request and Response APIs, and run on text dev and text start. They share the app's origin, so requests need no CORS setup.
A route handler
Added: import { DEPLOYMENTS, createDeployment } from "../../../lib/deployments";Added: Added: export function GET(): Response {Added: return Response.json(DEPLOYMENTS);Added: }Added: Added: export async function POST(req: Request): Promise<Response> {Added: const deployment = createDeployment(await req.json());Added: return Response.json(deployment, { status: 201 });Added: }Call it from the console prompt while text dev is running:
await (await fetch("/api/deployments")).json()Fetching in a page
Fetch in an effect and show a <Spinner> until the data arrives. r refetches; n posts a new deployment, which shows up as building and turns ready once the build finishes.
Removed: import { DEPLOYMENTS } from "../../lib/deployments";Added: import { useEffect, useState } from "react";Added: import { Spinner, useInput } from "@textjs/next";Added: import type { Deployment } from "../../lib/deployments";export default function DeploymentsPage() {Added: const [rows, setRows] = useState<Deployment[]>([]);Added: const [loading, setLoading] = useState(true);Added: Added: const refetch = async () => {Added: setLoading(true);Added: setRows(await (await fetch("/api/deployments")).json());Added: setLoading(false);Added: };Added: useEffect(() => void refetch(), []);Added: Added: useInput((e) => {Added: if (e.input === "r") void refetch();Added: if (e.input === "n") void fetch("/api/deployments", { method: "POST", body: "{}" }).then(refetch);Added: });Added: Added: if (loading) return <Spinner label="Loading deployments…" />; return ( <Box className="flex-col">Removed: {DEPLOYMENTS.map((d) => (Added: {rows.map((d) => ( <Box key={d.id} className="flex-row h-1 gap-2">Added: <Status status={d.status} /> …Loading and error states
Instead of handling loading by hand, a loading.tsx next to the page is shown while it suspends, and error.tsx catches anything it throws. Both are plain components:
import { Spinner } from "@textjs/next";export default function Loading() { return <Spinner label="Loading deployments…" />;}Streaming
Handlers can return a ReadableStream, which is how build logs stream into the dashboard line by line. See Route Handlers for server-sent events and proxying a local daemon.
Try it
This is Acme Deploys at the end of the chapter, running in DevTools. Click the pad, then use the keys below.
Open the console
The app renders in DevTools. Then click here and press r.
Open on a desktop
This demo renders in the Chrome DevTools console, which needs a desktop browser.
Quiz
2 questionsAnswer every question to complete the chapter.