Next.js
This guide mounts the Porulle Hono app inside a Next.js App Router catch-all route. The result: your Next.js frontend and the commerce API run in the same process, with no separate server, proxy, or CORS configuration.
Install
Section titled “Install”bun add @porulle/core @porulle/adapter-postgres hono postgres drizzle-ormbun add -d drizzle-kitCreate the commerce config
Section titled “Create the commerce config”import { defineConfig, Ok, type PaymentAdapter } from "@porulle/core";import { postgresAdapter } from "@porulle/adapter-postgres";
const DATABASE_URL = process.env.DATABASE_URL ?? "postgres://localhost:5432/my_store";
const mockPayments: PaymentAdapter = { providerId: "mock-payments", async createPaymentIntent(p) { return Ok({ id: `pi_${Date.now()}`, status: "requires_capture", amount: p.amount, currency: p.currency, clientSecret: `secret_${Date.now()}` }); }, async capturePayment(id, amount) { return Ok({ id, status: "succeeded", amountCaptured: amount ?? 0 }); }, async refundPayment(_id, amount) { return Ok({ id: `re_${Date.now()}`, status: "succeeded", amountRefunded: amount }); }, async cancelPaymentIntent() { return Ok(undefined); }, async verifyWebhook() { return Ok({ id: "evt_mock", type: "payment.succeeded", data: {} }); },};
export default defineConfig({ storeName: "My Store", database: { provider: "postgresql" }, databaseAdapter: postgresAdapter({ connectionString: DATABASE_URL }), auth: { requireEmailVerification: false, apiKeys: { enabled: true }, trustedOrigins: ["http://localhost:3000"], }, payments: [mockPayments],});Replace mockPayments with a real adapter before production. See the Payment Adapter guide.
Mount as a Next.js API route
Section titled “Mount as a Next.js API route”Create a catch-all route that delegates all /api/* requests to the Hono app:
import { handle } from "hono/vercel";import { createServer } from "@porulle/core";import config from "../../../../commerce.config";
const { app } = await createServer(config);
export const GET = handle(app);export const POST = handle(app);export const PUT = handle(app);export const PATCH = handle(app);export const DELETE = handle(app);All Porulle routes are now available at /api/* inside your Next.js app.
Add drizzle.config.ts
Section titled “Add drizzle.config.ts”import { defineConfig } from "drizzle-kit";
export default defineConfig({ dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL ?? "postgres://localhost:5432/my_store", }, schema: [ "./node_modules/@porulle/core/src/kernel/database/schema.ts", "./node_modules/@porulle/plugin-*/src/schema.ts", ],});Push the database schema
Section titled “Push the database schema”createdb my_storebunx drizzle-kit push --config drizzle.config.tsVerify
Section titled “Verify”bun run devcurl http://localhost:3000/api/catalog/entitiesExpected: { "success": true, "data": [] }. The OpenAPI spec is at http://localhost:3000/api/doc.
Exclude native binaries from Turbopack
Section titled “Exclude native binaries from Turbopack”If Turbopack fails to bundle postgres or other native packages:
import type { NextConfig } from "next";
const nextConfig: NextConfig = { serverExternalPackages: [ "@porulle/core", "drizzle-orm", "postgres", ],};
export default nextConfig;Add the typed SDK
Section titled “Add the typed SDK”With the dev server running, generate types from your OpenAPI spec:
bun add @porulle/sdk @tanstack/react-query openapi-react-querybun add -d openapi-typescriptbunx @porulle/sdk generate --url http://localhost:3000/api/doc --output src/generated/api-types.tsCreate a typed client. Use an empty baseUrl so SDK calls route through the same Next.js process:
import { createClient } from "@porulle/sdk";import { createCommerceHooks } from "@porulle/sdk/react";import { QueryClient } from "@tanstack/react-query";import type { paths } from "@/generated/api-types";
export const client = createClient<paths>({ baseUrl: "", auth: { type: "cookie" },});
export const commerce = createCommerceHooks(client);
export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 30_000, refetchOnWindowFocus: false }, },});Add a script to package.json for regenerating types:
{ "scripts": { "db:push": "bunx drizzle-kit push --config drizzle.config.ts", "sdk:generate": "bunx @porulle/sdk generate --url http://localhost:3000/api/doc --output src/generated/api-types.ts" }}Deploy to Vercel
Section titled “Deploy to Vercel”The Hono Vercel preset deploys the catch-all route as a serverless function automatically. No additional configuration is needed beyond setting environment variables:
DATABASE_URL— PostgreSQL connection string (Neon, Supabase, etc.)BETTER_AUTH_SECRET— generate withopenssl rand -hex 32BETTER_AUTH_URL— your app’s public URL
See the Deployment guide for Vercel-specific project settings and the conditional exports requirement.
Related
Section titled “Related”- Typed SDK Client guide — full SDK usage including React hooks
- Authentication guide — session cookies and trusted origins for browser requests
- Deployment guide — Vercel, Cloudflare Workers, Docker