Data & Route Handlers

Serve deployments from app/api, fetch them in a page and post new ones.

This chapter's app is running in your console. Open it with ⌘⌥JCtrl+Shift+J, then try it.
Watch the chapter3:32 · narrated · captions

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

app/api/deployments/route.ts+10ts
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.

app/deployments/page.tsx+21-2tsx
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:

app/deployments/loading.tsxtsx
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.

▼ 06 · Data & Route Handlers

Open the console

⌘⌥J
CtrlShiftJ

The app renders in DevTools. Then click here and press r.

RrefetchNnew deployment

Open on a desktop

This demo renders in the Chrome DevTools console, which needs a desktop browser.

Quiz

2 questions

Answer every question to complete the chapter.

What do route handlers return?

 

Which file is shown while a route suspends?