Quick start
This page builds a Next.js app with one collection, REST routes at /api and the admin UI at /admin. The database is PGlite (Postgres in WASM), stored in ./.pglite. You need Node.js 22.18 or later and pnpm.
Create a Next.js app
--no-cache-components is required. Quickben pages do not support Cache Components yet.
pnpm create next-app@latest my-app --yes --no-cache-components
cd my-appInstall Quickben
pnpm add @quickben/core @quickben/next @quickben/admin @quickben/db-drizzle drizzle-orm @electric-sql/pgliteThe packages are not on npm yet. Until the first release, build them from the repository and install the tarballs:
git clone https://github.com/vialabs-net/quickben.git ../quickben
cd ../quickben
pnpm install
pnpm turbo run build --filter='./packages/*'
for p in core next admin db-drizzle; do (cd packages/$p && pnpm pack --pack-destination ../../tarballs); done
cd ../my-app
pnpm add ../quickben/tarballs/*.tgz drizzle-orm @electric-sql/pgliteWrite the config
import { defineCollection, defineConfig, devAuth, fields as f } from "@quickben/core";
import { drizzleAdapter } from "@quickben/db-drizzle";
import { singleton } from "@quickben/next";
const posts = defineCollection({
slug: "posts",
fields: {
title: f.text({ required: true, maxLength: 200 }),
status: f.select({ options: ["draft", "published"], required: true, default: "draft" }),
body: f.textarea(),
},
access: {
read: () => true,
create: ({ user }) => user !== null,
update: ({ user }) => user !== null,
delete: ({ user }) => user !== null,
},
admin: { useAsTitle: "title", defaultColumns: ["title", "status", "updatedAt"] },
});
export default defineConfig({
db: singleton("db", () =>
drizzleAdapter({ dialect: "pglite", dataDir: "./.pglite", push: true }),
),
collections: [posts],
auth: devAuth(),
});| Line | Effect |
|---|---|
singleton | One database client per process. Route handlers and pages share it, also across dev reloads. |
push: true | Creates missing tables at startup. Use migrations for real data. |
devAuth() | Reads the user from the QUICKBEN_DEV_USER variable or the x-quickben-user header. Development only. |
Add /.pglite to .gitignore.
Add the route handlers
import { createRouteHandlers } from "@quickben/next";
import config from "../../../quickben.config";
export const { GET, POST, PATCH, DELETE } = createRouteHandlers(config);Add the admin page
import { createAdminPage, handleAdminRequest } from "@quickben/admin";
import config from "../../../quickben.config";
import type { AdminRequest } from "@quickben/admin";
async function serverFunction(request: AdminRequest) {
"use server";
return handleAdminRequest(config, request);
}
export default createAdminPage(config, { serverFunction });import "@quickben/admin/styles.css";
import type { ReactNode } from "react";
export default function AdminLayout({ children }: Readonly<{ children: ReactNode }>) {
return children;
}Admin UI explains the serverFunction lines.
Configure Next.js and the dev user
Add this line to the nextConfig object in next.config.ts:
serverExternalPackages: ["@quickben/db-drizzle"],It loads the adapter with Node.js instead of the bundler. Without it, the build fails on the database drivers you did not install (pg, @libsql/client).
QUICKBEN_DEV_USER='{"id":"dev","email":"dev@example.com","roles":["admin"]}'Run it
pnpm dev| URL | Result |
|---|---|
| http://localhost:3000/admin | Dashboard. Open Posts, then “New post”. |
| http://localhost:3000/api/posts | {"docs":[],"limit":20,…} |
| http://localhost:3000/api/\_meta | Collections and fields the user can read. |
Create a post over REST:
curl -X POST http://localhost:3000/api/posts \
-H 'content-type: application/json' \
-d '{"title":"Hello"}'Read data in a page
Server components call the local API directly. Access rules apply with the current user.
import { getQuickben, getRequestContext } from "@quickben/next";
import config from "../../quickben.config";
export default async function PostsPage() {
const qb = await getQuickben(config);
const posts = await qb.find(
"posts",
{ where: { status: { eq: "published" } }, sort: "-createdAt" },
await getRequestContext(config),
);
return (
<ul>
{posts.docs.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}posts.docs is typed from the fields: { id, createdAt, updatedAt, title: string, status: "draft" | "published", body: string | null }[].
What runs where
| File | Package | Calls |
|---|---|---|
quickben.config.ts | @quickben/core | Collections, access rules, database, auth. |
app/api/[...quickben]/route.ts | @quickben/next | qb.find, qb.create, … per HTTP request. |
app/admin/[[...segments]]/page.tsx | @quickben/admin | Same local API, with the user from config.auth. |
next.config.ts | Next.js | Keeps the database drivers out of the bundle. |
Next steps
- Collections and Fields
- Access control: replace
user !== nullwith real rules. - Auth adapters: replace
devAuth()before you deploy. - Drizzle adapter: Postgres, SQLite and migrations.
- Playground: a larger example with relations, roles and modules.