Skip to Content
DocsQuick start

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-app

Install Quickben

pnpm add @quickben/core @quickben/next @quickben/admin @quickben/db-drizzle drizzle-orm @electric-sql/pglite

The 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/pglite

Write the config

quickben.config.ts
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(), });
LineEffect
singletonOne database client per process. Route handlers and pages share it, also across dev reloads.
push: trueCreates 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

app/api/[...quickben]/route.ts
import { createRouteHandlers } from "@quickben/next"; import config from "../../../quickben.config"; export const { GET, POST, PATCH, DELETE } = createRouteHandlers(config);

Add the admin page

app/admin/[[...segments]]/page.tsx
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 });
app/admin/layout.tsx
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).

.env.local
QUICKBEN_DEV_USER='{"id":"dev","email":"dev@example.com","roles":["admin"]}'

Run it

pnpm dev
URLResult
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.

app/posts/page.tsx
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

FilePackageCalls
quickben.config.ts@quickben/coreCollections, access rules, database, auth.
app/api/[...quickben]/route.ts@quickben/nextqb.find, qb.create, … per HTTP request.
app/admin/[[...segments]]/page.tsx@quickben/adminSame local API, with the user from config.auth.
next.config.tsNext.jsKeeps the database drivers out of the bundle.

Next steps

Last updated on