In plainweb, a handler is a function that processes an incoming request and returns a response.
plainweb simplifies common use cases by inferring the response type based on the returned value.
Return a JSX.Element
or a string to render an HTML response. In plainweb, JSX.Element
is essentially string | Promise<string>
.
import { Handler } from "plainweb";
export const GET: Handler = async () => {
return (
<html>
<body>
<div>Hello world</div>
</body>
</html>
);
};
For more control, use the html
helper function:
import { Handler, html } from "plainweb";
export const GET: Handler = async () => {
return html(
<html>
<body>
<div>Hello world</div>
</body>
</html>,
{ status: 200 }
);
};
Return a plain object to generate a JSON response:
import { Handler } from "plainweb";
export const GET: Handler = async () => {
return { hello: ["world1", "world2"] };
};
For more control, use the json
helper function:
import { Handler, json } from "plainweb";
export const GET: Handler = async () => {
return json({ hello: ["world1", "world2"] }, { status: 200 });
};
Note: The returned object must be JSON-serializable (no functions or promises).
Use the redirect
function to perform redirects:
import { Handler, redirect } from "plainweb";
export const GET: Handler = async () => {
return redirect("/admin");
};
For full control, you can access the Express Response
object directly:
import { Handler } from "plainweb";
export const GET: Handler = async ({ res }) => {
return () => {
res.status(200).send("Hello world");
};
};
For a route like routes/orgs/[orgId]/users/[userId].tsx
, access parameters using req.params
:
import { Handler } from "plainweb";
export const GET: Handler = async ({ req }) => {
return (
<div>
User id {req.params.userId} in org {req.params.orgId}
</div>
);
};
Access query parameters via req.query
. It's recommended to parse them using zod
:
import { z } from "zod";
import { Handler } from "plainweb";
export const GET: Handler = async ({ req }) => {
const schema = z.object({ sort: z.string() });
const result = schema.safeParse(req.query);
if (!result.success) {
return <div>Invalid sort parameter</div>;
}
return <div>Sorted by {result.data.sort}</div>;
};
Use zod-form-data to parse form data:
import { zfd } from "zod-form-data";
import { Handler } from "plainweb";
export const POST: Handler = async ({ req }) => {
const schema = zfd.formData({ value: zfd.text() });
const result = schema.safeParse(req.body);
if (result.success && result.data.value === "ping") {
return <div>Pong!</div>;
}
return <div>Ping?</div>;
};
export const GET: Handler = async () => {
return (
<form hx-post="/ping">
<input type="hidden" name="value" value="ping" />
<button>Ping</button>
</form>
);
};
plainweb supports streaming responses using Suspense
, similar to React:
import { Suspense } from "@kitajs/html/suspense";
import { Handler, stream } from "plainweb";
async function HelloDelayed() {
await new Promise((resolve) => setTimeout(resolve, 5000));
return <div>Hello 5 seconds later!</div>;
}
export const GET: Handler = async () => {
return stream((rid) => (
<Suspense
rid={rid}
fallback={<div>Loading...</div>}
catch={() => <div>Something went wrong</div>}
>
<HelloDelayed />
</Suspense>
));
};
This allows rendering parts of the page immediately while slower components load asynchronously.
Layouts in plainweb are JSX components that wrap page content. They're simple to implement and use:
export default function Layout({ children }: Html.PropsWithChildren<{}>) {
return (
<>
{"<!doctype html>"}
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>My App</title>
</head>
<body>{children}</body>
</html>
</>
);
}
You can create a root layout for global styles and scripts, and nest more specific layouts within it:
import RootLayout from "app/components/root-layout";
import AppLayout from "app/components/app-layout";
export const GET: Handler = async () => {
return (
<RootLayout>
<AppLayout>
<h1>Welcome to my app!</h1>
</AppLayout>
</RootLayout>
);
};