Stack
Hono Prisma Starter
Connecting Hono to Prisma takes about four lines. Everything after those four lines is what this walkthrough is about.
The first Hono and Prisma route needs little code: two imports, a client, and a route. Many repositories called some variation of hono prisma starter stop at that connection. A production application also needs a query strategy, typed client-server calls, local infrastructure, and deployment configuration.
This is a walkthrough of the open-source Hype Stack core, which adds that surrounding setup. After the install and three commands, you have Hono, Prisma, Kysely, and Postgres running behind a React frontend that knows your route types.
Everything in this guide is MIT licensed and the three steps cost nothing to follow. The last section is the honest part: what the core does not have, and what the cards further down the page cost.
What you need installed
Node 20 or newer, pnpm 9 or newer, and Docker. The CLI checks all three before it touches the disk and prints what it found. A missing Docker is a warning rather than a stop, because scaffolding works fine without it, but step two is where you get a database, so you will want it.
Step 1: create the project
npx @hype-stack/cli create my-appIt asks for the project name, the parent folder to drop it in, your editors, and a color theme for the frontend and for
the admin app. The editor question does more than it looks like it does. It decides where the bundled coding rules land,
.cursor/rules, AGENTS.md, .claude/rules, .github/instructions, or .windsurf/rules, so whichever agent you use
starts out knowing how this codebase is organized instead of inventing its own conventions in your repository. Tick more
than one and every format ships, which is what you want when half the team is in Cursor and the other half is in Claude
Code. The bundled skills come along either way, in the shared .agents/skills folder every one of those agents reads.
The CLI then clones the template, deletes its git history and commits a fresh repository, and rewrites the @hype-stack
scope to your project name everywhere it appears: package names, imports, tsconfig path mappings, the backend
Dockerfile, the page title. Then it writes stack.json, runs pnpm install, and copies every .env.example to a real
.env with local development values already in it.
- apps/frontend/React 19, TanStack Router, Tailwind v4, shadcn/ui, and an Electron build behind a flag
React 19, TanStack Router, Tailwind v4, shadcn/ui, and an Electron build behind a flag
- apps/admin/a second React app on its own port, one page in it so far
a second React app on its own port, one page in it so far
- apps/backend/
- docker-compose.ymlPostgres, Valkey, and object storage for local dev
Postgres, Valkey, and object storage for local dev
- prisma/schema/base.prisma only: the two generator blocks and no models yet
base.prisma only: the two generator blocks and no models yet
- src/routes/the HTTP boundary, one folder per domain, chained in registerRoutes
the HTTP boundary, one folder per domain, chained in registerRoutes
- src/features/a vertical slice per domain: modules, db access, schemas, tests
a vertical slice per domain: modules, db access, schemas, tests
- src/db/postgres/where the generated Prisma client and Kysely types land
where the generated Prisma client and Kysely types land
- packages/enums/config and enums every app imports, so they cannot disagree
config and enums every app imports, so they cannot disagree
- stack.jsonyour workspace paths and the packs installed so far
your workspace paths and the packs installed so far
- apps/*/.envcopied from the examples, local defaults filled in
copied from the examples, local defaults filled in
The template is deliberately empty of product features, so you do not need to remove a demo application before adding your own domains.
Step 2: start Postgres, Valkey, and storage
The CLI offers to do this at the end of create. Say yes and it starts the containers, polls Postgres with pg_isready
until it accepts connections, then asks before applying migrations. To do it yourself, or later, from apps/backend:
docker compose up -d
pnpm exec prisma generateThere is nothing to migrate yet, which surprises people. base.prisma ships with the two generator blocks and no
models, and prisma/migrations holds only the lock file, so the first migration is the one you write in the next
section. prisma generate is worth running now anyway. Nx has it as a dependency of the dev servers, so pnpm dev
would trigger it, but running it here means your editor is not full of red imports while you read the code.
| Service | Port | What it is for |
|---|---|---|
| Postgres 17 with pgvector | 5436 | The database. Vector column type is already enabled, so embeddings do not need a second store. |
| Valkey | 6381 | Cache, Redis compatible, with a client wired in src/cache. |
| RustFS | 9000 | S3-compatible object storage, so uploads work locally without an AWS account. |
| RustFS console | 9001 | Web UI for browsing what you uploaded. |
Two things are worth knowing before you start the containers. The credentials in apps/backend/.env are development
defaults committed to the repository, so treat them as throwaway. And the container names and host ports are fixed,
which means a second Hype Stack project on the same machine collides with the first. The CLI recognizes that failure and
names the container to stop.
Step 3: run it
pnpm devNx starts three servers in parallel: the Hono API on port 3000, the Vite frontend on 4200, and the admin app on 4100. The React apps hot reload, the API rebuilds and restarts on change.
Moving a port is a two-file job, which is worth knowing before you go looking in the wrong one. The numbers are literals
in each app's configs/vite.web.config.mts. The matching FRONTEND_URL and ADMIN_URL in apps/backend/.env are what
the backend puts in links and allows through CORS, so change both or the two stop agreeing. To boot a single app instead
of all three, name it: nx serve @my-app/frontend.
That completes the setup. The rest of this page describes the architecture included in this hono prisma starter.
What is already wired
Included infrastructure
A real monorepo, not a folder convention
apps/frontend, apps/admin, apps/backend, and packages/enums are pnpm workspaces, and the two React apps import the backend as a dependency. That import makes the type bridge below possible, and it is fiddly enough to wire that most starters skip it.
Rules and skills for whatever agent you use
Editor rules ship with the template and get converted to your editor's format during create, and agent skills are pinned in skills-lock.json. A few project-specific lint rules back them up, and the GitHub Actions workflow that comes with the repository runs build, lint, typecheck, and the tests, so the conventions are checked rather than suggested.
Tests that run against a real database
Vitest, React Testing Library, and Playwright are configured. The backend also ships a second compose file on different ports, so pnpm test:setup boots temporary containers, applies migrations, and runs query tests against Postgres rather than a database mock.
The same app on the desktop
Electron Forge is configured for macOS, Windows, and Linux, and one VITE_APP_TYPE flag decides whether the React app boots as a web SPA or a desktop build. You can leave the desktop path unused if the product only targets the web.
Adding your first feature
The loop below shows how a feature moves from the database schema to a typed frontend call. Step two differs from a plain Prisma setup because it generates both database clients.
One feature, end to end
Add the model
prisma/schema/ holds one file per feature rather than one long schema, so a new domain is a new file. Write the model there.
Migrate, which regenerates both clients
pnpm exec prisma migrate dev writes the SQL migration, updates the Prisma client, and regenerates the Kysely DB interface in the same run. Both typed clients come from the same schema.
Write the read and the write
Reads go in the feature's db/queries on postgres.qb, which is Kysely. Inserts go in db/mutations through Prisma. Anything transactional uses postgres.qb.transaction().
Add the route and chain it
A thin handler in src/routes/<domain>, a Zod schema through the validate() middleware, and one .route() line in registerRoutes. The schema is the only place the payload shape is written down.
Call it from the frontend
useFetch(sdk.notes.$get) is typed from that Zod schema. There is no API client to generate or maintain, and deleting the route produces a type error at the call site.
No build step
The loop generates the Prisma client and Kysely database types from the same schema. The API client is inferred from the route tree.
Why Prisma and Kysely both
Prisma provides the schema and migrations. For reads with several joins and conditional filters, this stack uses Kysely to keep the SQL structure explicit rather than expressing the query through the Prisma API.
Two generator blocks in prisma/schema/base.prisma do that:
generator client {
provider = "prisma-client-js"
output = "../../src/db/postgres/generated/client"
previewFeatures = ["postgresqlExtensions"]
}
generator kysely {
provider = "prisma-kysely"
output = "../../src/db/postgres/types"
fileName = "types.ts"
enumFileName = "enums.ts"
camelCase = true
}Running an ORM and a query builder side by side means two mental models and two sets of types that slowly disagree about what a column is called.
prisma-kysely reads the same schema and emits the Kysely DB interface next to the Prisma client. Neither is hand-maintained, so neither can drift, and prisma generate produces both in one pass.
The payoff shows up on the reads that grow:
let query = postgres.qb.selectFrom("project").selectAll();
if (search) {
query = query.where((eb) => eb.or([eb("name", "ilike", `%${search}%`), eb("description", "ilike", `%${search}%`)]));
}
return getPaginatedQuery({ query, limit, offset });That filter is applied only when the request includes a search value, and the query still reads like the SQL it becomes.
One operational note while you are looking at this. The Kysely instance is attached to the Prisma client as an
extension, so there is one thing to import but two pg pools open against the same DATABASE_URL. Count them twice
when you set max_connections or deploy behind a connection limit.
Types cross HTTP without a codegen step
The other boundary is the network. In this stack, it does not require a folder of hand-maintained api/*.ts wrappers.
Here the whole route tree is a single type. registerRoutes chains every .route() call and returns the composed app,
and the SDK type is derived from its return type:
export type ApiRoutesSdk = HonoAppToHyperFetch<ReturnType<typeof registerRoutes>, ApiClient>;The frontend imports that type and hands it to HyperFetch with createSdk<typeof client, ApiRoutesSdk>(client). Because
the type is inferred from ReturnType<typeof registerRoutes> rather than written down, there is no API code generation
step after changing a route. Renaming a backend field produces a type error at affected frontend call sites.
What no codegen means here
"No codegen" is a claim about the API surface, not the whole stack. prisma generate is real code generation and you
do run it after a schema change, which is what produces the Prisma client and the Kysely types. The distinction that
matters is that nothing regenerates an API client, so the backend and the frontend cannot be out of sync because of a
stale build artifact.
What is not in the box
There is no login, no user table, no sessions, no password reset, no organizations, and no billing. The admin app boots
but has a single page in it. create gives you an empty application with the architecture already decided, which is a
different thing from a product with the features stripped out.
Plan around that honestly: after step three you have a backend, a frontend, and nobody signed in. Nothing in the core assumes a particular auth provider, so writing your own is a normal amount of work rather than a fight with the template. If you would rather install something, Better Auth has a Prisma adapter and sits on this schema without much argument. It is not ours, it does not arrive with the template, and you run it yourself. Skip Lucia, which used to be the other answer to this question: the maintainer retired the package and turned the project into a guide for writing sessions from scratch. Read it if you want to own that code, but do not go looking for a dependency.
One thing to be clear about before you scroll. The pack cards below are paid and license-gated, and the template cards are built on those packs. The three steps on this page need none of them.
Below that: the short version of what Hype Stack is, recordings of real apps running on this core, and the questions people usually ask before they run the first command.
What is Hype Stack?
Every product starts with the same month of work nobody pays you for: sign-up and login, teams and permissions, taking payments, notifications, an admin panel to run the business. Hype Stack is that month, already built and tested. Start from the free open-source app, add the pieces you need with one command, and keep going on the part that is actually your idea.
Everything lands as real code in your own repository, so there is nothing to rent and nothing anyone can switch off. For the engineers: React 19, Hono, Postgres, and a desktop build, typed end to end.
See it running
Templates are curated project starters built on this stack: a layout, feature packs, a custom theme, and bonus pages. The previews below are recordings of the real apps.
Packs in this stack
Every pack ships real source code: frontend, backend, and admin surfaces where the feature needs them. The ones this stack installs come first; the rest can be composed in later.