Source: https://www.hype-stack.dev/stacks/hono-prisma-starter

# Hono Prisma Starter with Postgres, Typed Client and Docker

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

```bash
npx @hype-stack/cli create my-app
```

It 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.

**my-app** — written during create, not cloned

- apps/frontend/ — 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
- apps/backend/
  - docker-compose.yml — Postgres, Valkey, and object storage for local dev
  - prisma/schema/ — base.prisma only: the two generator blocks and no models yet
  - src/routes/ — the HTTP boundary, one folder per domain, chained in registerRoutes
  - src/features/ — a vertical slice per domain: modules, db access, schemas, tests
  - src/db/postgres/ — where the generated Prisma client and Kysely types land
- packages/enums/ — config and enums every app imports, so they cannot disagree
- stack.json (added) — your workspace paths and the packs installed so far
- apps/*/.env (added) — copied from the examples, local defaults filled in

## 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`:

```bash
docker compose up -d
pnpm exec prisma generate
```

There 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

```bash
pnpm dev
```

Nx 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**

1. **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.
2. **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.
3. **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().
4. **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.
5. **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)

## 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:

```prisma
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
}
```

- **Two query layers:** 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.
  **One schema, both generated:** 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:

```ts
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:

```ts
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.

## Common gotchas

The three steps fail in a small number of predictable ways. Each one has a one-line fix.

**If step two or three does not do what the page says**

- **Docker is not running** Step two needs the daemon up before docker compose can start Postgres, Valkey and storage. The CLI warns at create time and does not stop, so this is the first thing to check when the backend cannot reach the database.
- **Port 3000 or 4200 is taken** The API binds 3000 and the web app 4200. Another project on either port makes the dev command look hung. Stop that process, or change the port in the backend and frontend env files together.
- **The schema changed and the types did not** After editing a Prisma model, run the generate script. It produces the Prisma client and the Kysely types in one go; a route that will not typecheck after a model edit is almost always this.
- **A phone cannot reach localhost** The mobile app's env points at localhost, which is right for the simulator and wrong for a physical device. Put the machine's LAN address in the mobile env instead.
- **The env file is the example** create copies every .env.example to a real .env. If you cloned by hand instead, nothing was copied, and the backend starts with no database URL.

_The docs keep a longer troubleshooting page grouped by setup, database, auth, billing and deploy._

## Using it with a coding agent

The editor question in step one is the part people skip. Answer it, and the repository ships the conventions as files
the agent reads: `.cursor/rules` for Cursor, `.claude/rules` for Claude Code, `AGENTS.md` for Codex, with the scoped
rules carrying a `paths` field so a backend rule loads only on backend files. The eleven bundled skills land in
`.agents/skills` for every agent at once.

Two things are worth doing before the first prompt. Point the agent at a route file and a query file and ask it to
describe the conventions back to you; if it gets them right, it will follow them. And install the MCP server with
`npx @hype-stack/cli@latest mcp install`, so "add login" or "add billing" becomes a real installer call instead of code
written from memory. The [Claude Code templates](/stacks/claude-code-templates) and
[best MCP servers for Claude Code](/stacks/best-mcp-servers-for-claude-code) pages go into both.

## 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 sits on top of this backend in the other starters:
[Better Auth SaaS Template with Organizations Built In](/stacks/better-auth-saas-template) and
[React Stripe Boilerplate](/stacks/react-stripe-boilerplate), and the agent-shaped version in
[AI Agent Boilerplate](/stacks/ai-agent-boilerplate).
