Framework adapters
Mount the handler the client calls. One fetch handler, three optional packages, and a base path that has to match on both ends.
The client sends HTTP. Something has to answer it. That something is one handler, and every runtime mounts the same one.
The handler
createFetchHandler takes your app and gives you a function from a Request
to a Response or null. All four starter templates use it directly, so this
is the shape you already have if you scaffolded with create-questpie.
import { createFetchHandler } from "questpie/http";
import { app } from "#questpie";
const handler = createFetchHandler(app, { basePath: "/api" });
const handle = async (request: Request) => {
const response = await handler(request);
return response ?? new Response("Not found", { status: 404 });
};
export const GET = handle;
export const POST = handle;
export const PATCH = handle;
export const PUT = handle;
export const DELETE = handle;The handler answers `null` outside its base path
It does not throw and it does not 404 for you. A null means the request was
not yours, so your framework can carry on routing. Turn it into a response
yourself, as above, when the handler owns the whole prefix.
The basePath you pass here is the one the client needs. Use "/api" when the
same app also serves a frontend, and "/" for a headless API. Get them out of
step and every call 404s.
// server
createFetchHandler(app, { basePath: "/api" });
// client
createClient<AppConfig>({ baseURL, basePath: "/api" });The runtime packages
Three packages wrap the same handler for a framework you already have. None of the starters install one, because each template mounts the handler itself.
| Package | Import | Gives you |
|---|---|---|
@questpie/next | questpieNextRouteHandlers | The seven App Router verb exports |
@questpie/hono | questpieHono from /server | A Hono app with a catch-all route |
@questpie/elysia | questpieElysia from /server | An Elysia plugin to .use() |
Next
import { questpieNextRouteHandlers } from "@questpie/next";
import { app } from "#questpie";
export const { GET, POST, PATCH, DELETE, PUT, OPTIONS, HEAD } =
questpieNextRouteHandlers(app, { basePath: "/api" });All seven exports are the same function. questpieNext(app, config?) is the
single-handler form if you want to wrap it yourself. Both turn an out-of-base
request into a 404 with a JSON body. The package has one export path, so
there is no /server subpath here.
Hono
import { Hono } from "hono";
import { questpieHono } from "@questpie/hono/server";
import { app } from "#questpie";
const server = new Hono();
server.route("/", questpieHono(app, { basePath: "/api" }));
export default server;questpieHono returns a Hono app that answers ${basePath}/*, so mount it at
"/" and let the base path do the narrowing. It reads appContext and user
off the Hono context when they are set. Add questpieMiddleware(app) to set
them, and to get app, appContext and user on your own routes too.
Elysia
import { Elysia } from "elysia";
import { questpieElysia } from "@questpie/elysia/server";
import { app } from "#questpie";
const server = new Elysia().use(questpieElysia(app, { basePath: "/api" }));
export default server;questpieElysia returns a plugin with a /* route under prefix: basePath.
Out-of-base requests come back as a 404 with a JSON body.
Typing custom routes through Hono RPC
@questpie/hono/client ships createClientFromHono. It builds an hc client
for your own Hono routes and copies collections and globals onto it.
import { createClientFromHono } from "@questpie/hono/client";
const client = createClientFromHono<AppType, AppConfig>({
baseURL: "http://localhost:3000",
basePath: "/api",
});It carries CRUD only
collections and globals are the only members copied over. routes,
search, realtime and channels are not on the merged object, and its
config has no getAuthHeaders. Keep a normal createClient around if you
need any of that.
Next
Client SDK is the method list this handler answers.