# SolidStart (/docs/guides/frameworks/solid-start)

> For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL.

Add Prisma ORM to a SolidStart app with orm init, seed a PostgreSQL database, serve users from an API route, and render them in a page.

Location: Guides > Frameworks > SolidStart

## Introduction [#introduction]

SolidStart is a full-stack framework for building reactive web apps with SolidJS. Its API routes and server functions run on the server, which is where you call Prisma ORM to read from a PostgreSQL database.

In this guide, you scaffold a SolidStart project, add Prisma ORM to it with `orm init`, initialize and seed a PostgreSQL database, serve users from an API route, and render them in a page with loading and error states. There is no `create-prisma` template for SolidStart, so this guide follows the add-to-an-existing-project path.

Every command, file, and response below was run end to end against a local PostgreSQL database.

> [!NOTE]
> Using Prisma ORM 7?
> 
> Prisma ORM 8 is the current release. Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/frameworks/solid-start](https://www.prisma.io/docs/guides/v7/frameworks/solid-start).

## Prerequisites [#prerequisites]

* [Node.js](https://nodejs.org) 24 or later (the SolidStart 2 template requires it)
* A PostgreSQL connection string, or nothing at all: `npx create-db@latest` can create a [Prisma Postgres](https://www.prisma.io/docs/postgres) database for you

## Use with your agent [#use-with-your-agent]

To delegate this guide to your coding agent, copy the prompt below and hand it over:

```text
Create a new SolidStart app with Prisma ORM, seed it, and serve users from an API route and a page.

1. Scaffold: `npm init solid@latest my-solid-prisma-app -- -s --v2 -t basic --ts`. Delete the `pnpm-lock.yaml` the template ships (otherwise Prisma picks pnpm), then `cd my-solid-prisma-app` and run `npm install`.
2. Add Prisma ORM: `npx prisma@latest orm init --yes --target postgres --authoring psl`. Then run `npx prisma@latest init` so the Prisma agent skills are installed and stay current, and use them. Get a database connection string: use the one I give you, or create a Prisma Postgres database with `npx create-db@latest` and show me the claim URL it prints. Write it to `.env` as `DATABASE_URL`.
3. Run `npx prisma@latest db init` to create the tables from `src/prisma/contract.prisma`.
4. Add `src/prisma/seed.ts` that creates two users with posts through `db.orm.public.User.create` and `db.orm.public.Post.create`, closes with `await db.runtime().close()`, and run it once with `node src/prisma/seed.ts`.
5. Add `src/routes/api/users.ts` with a `GET` handler that returns `db.orm.public.User.include("posts").all()` as JSON, and replace `src/routes/index.tsx` with a page that loads the same query through a `"use server"` function wrapped in `query` and `createAsync`, with `<Suspense>` for loading and `<ErrorBoundary>` for errors, following https://www.prisma.io/docs/guides/frameworks/solid-start.md. Catch Prisma errors inside the server function and rethrow a plain `Error`.
6. Start `npm run dev` in the background, wait until it reports ready, verify `curl http://localhost:3000/api/users` returns the seeded users and `curl http://localhost:3000/` includes their names, then stop the dev server.
```

## 1. Scaffold the SolidStart project [#1-scaffold-the-solidstart-project]

Create a new SolidStart 2 project from the `basic` TypeScript template:

  

#### bun

```bash
bunx create-solid my-solid-prisma-app -s --v2 -t basic --ts
```

#### pnpm

```bash
pnpm create solid my-solid-prisma-app -s --v2 -t basic --ts
```

#### yarn

```bash
yarn create solid my-solid-prisma-app -s --v2 -t basic --ts
```

#### npm

```bash
npm init solid@latest my-solid-prisma-app -- -s --v2 -t basic --ts
```

```text no-copy
◇  Project created 🎉
◇  To get started, run: ───╮
│  cd my-solid-prisma-app  │
│  npm install             │
│  npm run dev             │
```

The flags skip the prompts: `-s` picks SolidStart, `--v2` picks the stable SolidStart 2 line, `-t basic` picks the template, and `--ts` picks TypeScript. Without them, the CLI asks the same questions interactively.

The template ships a `pnpm-lock.yaml`. Delete it before you continue if you use npm; otherwise `orm init` reads the lockfile and installs Prisma with pnpm:

```bash
cd my-solid-prisma-app
rm pnpm-lock.yaml
```

Install the dependencies:

  

#### bun

```bash
bun install
```

#### pnpm

```bash
pnpm install
```

#### yarn

```bash
yarn install
```

#### npm

```bash
npm install
```

This writes `package-lock.json`, so the next step picks npm.

## 2. Add Prisma ORM [#2-add-prisma-orm]

Run `orm init` from the project root. The flags preselect PostgreSQL and the Prisma Schema Language; drop `--yes` and `--authoring` to answer those questions interactively:

  

#### bun

```bash
bunx prisma@latest orm init --yes --target postgres --authoring psl
```

#### pnpm

```bash
pnpm dlx prisma@latest orm init --yes --target postgres --authoring psl
```

#### yarn

```bash
yarn dlx prisma@latest orm init --yes --target postgres --authoring psl
```

#### npm

```bash
npx prisma@latest orm init --yes --target postgres --authoring psl
```

```text no-copy
Updated tsconfig.json with required compiler options.
✔ npm add @prisma/orm-postgres dotenv
✔ npm add -D prisma@latest @types/node
✔ npm add -D @prisma/cli-engine@0.3.0
✔ Emit the contract
│  target:     postgres
│  authoring:  psl
│  schema:     src/prisma/contract.prisma
written
├─ src/prisma/contract.prisma
├─ prisma.config.ts
├─ src/prisma/db.ts
├─ prisma-8.md
├─ .env.example
├─ tsconfig.json
├─ .gitignore
├─ .gitattributes
└─ package.json
✔ Done. Open prisma-8.md to get started.
```

The command installs the runtime, writes the Prisma ORM files into the SolidStart project, and emits `src/prisma/contract.json` and `src/prisma/contract.d.ts`, the artifacts your queries are type-checked against. Three files matter for the rest of this guide:

* `src/prisma/contract.prisma`: a starter contract with `User` and `Post` models and a one-to-many relation between them
* `src/prisma/db.ts`: the Prisma ORM client, constructed once and imported by your routes
* `prisma.config.ts`: tells the CLI where the contract lives and reads `DATABASE_URL` from `.env`

The starter contract is the same shape the Prisma ORM 7 guide asked you to write by hand:

```prisma title="src/prisma/contract.prisma"
// use prisma-8

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  username  String?
  name      String?
  posts     Post[]
  createdAt TimestamptzString @default(now())
  updatedAt temporal.updatedAtString()
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt TimestamptzString @default(now())
  updatedAt temporal.updatedAtString()
}
```

And the scaffolded client is all the wiring the app needs. There is no `prisma generate`, no generated client directory, and no driver adapter; the emitted contract and the runtime package replace all three:

```typescript title="src/prisma/db.ts"
import 'dotenv/config';
import postgres from '@prisma/orm-postgres/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };

export const db = postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL']!,
});
```

`orm init` also adds `"node"` to the `types` array and `resolveJsonModule` to `tsconfig.json` so the JSON contract import type-checks, and leaves the SolidStart settings (`jsx`, `jsxImportSource`, the `~/*` path alias) alone.

Now set the database connection. Copy `.env.example` to `.env` and replace the placeholder with your own PostgreSQL connection string, or create a Prisma Postgres database with `npx create-db@latest`; it prints a connection string and a claim URL you can open to keep the database:

```bash title=".env"
DATABASE_URL="postgres://user:password@localhost:5432/mydb"
```

Both `prisma.config.ts` and `db.ts` import `dotenv/config`, so the CLI and the dev server read the same file.

## 3. Initialize the database [#3-initialize-the-database]

Create the tables the contract declares and sign the database:

  

#### bun

```bash
bunx prisma@latest db init
```

#### pnpm

```bash
pnpm dlx prisma@latest db init
```

#### yarn

```bash
yarn dlx prisma@latest db init
```

#### npm

```bash
npx prisma@latest db init
```

```text no-copy
✔ Introspecting database schema
✔ Planning migration
✔ Initialising database across spaces
│  contract:  src/prisma/contract.json
│  database:  postgres://****@localhost:5432/mydb

✔ Applied 5 operation(s) across 1 contract space

App space
├─ Create table "post"
├─ Create table "user"
├─ Add unique constraint on "user" (email)
├─ Create index "post_authorId_idx_e47547ed" on "post"
├─ Add foreign key "post_authorId_fkey" on "post"
└─ marker 1e8412e162dbbe69f4bb3bf8d07f0280ae67eaab15c34dcf201e67468315428d
```

`db init` replaces `prisma migrate dev` from Prisma ORM 7 for the first apply: it creates what is missing and records the contract hash in the database. Later schema changes go through [`db update`](https://www.prisma.io/docs/cli/db-update) for a direct development update or [`migration plan`](https://www.prisma.io/docs/cli/migration-plan) for a checked-in migration. To confirm the database matches the contract at any time, run `npx prisma@latest db verify`.

## 4. Seed the database [#4-seed-the-database]

Create `src/prisma/seed.ts`. It creates two users and their posts through the ORM API. `create()` returns the inserted row, so the user's `id` is available for the posts without a second query:

```typescript title="src/prisma/seed.ts"
import { db } from "./db.ts";

const users = [
  {
    name: "Alice",
    email: "alice@prisma.io",
    posts: [
      { title: "Join the Prisma Discord", content: "https://pris.ly/discord" },
      { title: "Prisma on YouTube", content: "https://pris.ly/youtube" },
    ],
  },
  {
    name: "Bob",
    email: "bob@prisma.io",
    posts: [{ title: "Follow Prisma on Twitter", content: "https://www.twitter.com/prisma" }],
  },
];

async function main() {
  for (const { posts, ...user } of users) {
    const created = await db.orm.public.User.create(user);
    for (const post of posts) {
      await db.orm.public.Post.create({ ...post, authorId: created.id });
    }
    console.log(`Seeded ${created.email} with ${posts.length} post(s)`);
  }
  await db.runtime().close();
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

Node.js 24 runs TypeScript directly, so no extra tooling is needed. Run the script once:

```bash
node src/prisma/seed.ts
```

```text no-copy
Seeded alice@prisma.io with 2 post(s)
Seeded bob@prisma.io with 1 post(s)
```

Running it a second time fails on the unique `email` constraint, which is expected. The script closes the connection pool at the end because a one-off script would otherwise keep the process alive; the app's routes never do this.

## 5. Serve users from an API route [#5-serve-users-from-an-api-route]

SolidStart maps files under `src/routes/api/` to HTTP endpoints. Create `src/routes/api/users.ts`:

```typescript title="src/routes/api/users.ts"
import { db } from "~/prisma/db";

export async function GET() {
  const users = await db.orm.public.User.include("posts").all();
  return Response.json(users);
}
```

Model access is namespace-qualified on PostgreSQL, so the `User` model is `db.orm.public.User`. `.include("posts")` eager-loads the relation and `.all()` returns the rows as an array.

Start the dev server:

  

#### bun

```bash
bun run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### npm

```bash
npm run dev
```

```text no-copy
  VITE v8.3.0  ready in 5509 ms

  ➜  Local:   http://localhost:3000/
  ➜  Network: use --host to expose
```

Then request the route:

```bash
curl http://localhost:3000/api/users
```

```json no-copy
[
  {
    "createdAt": "2026-09-10 21:45:05.701722+06",
    "email": "alice@prisma.io",
    "id": 1,
    "name": "Alice",
    "updatedAt": "2026-09-10 21:45:04.523+06",
    "username": null,
    "posts": [
      { "authorId": 1, "content": "https://pris.ly/discord", "createdAt": "2026-09-10 21:45:05.875837+06", "id": 1, "title": "Join the Prisma Discord", "updatedAt": "2026-09-10 21:45:05.874+06" },
      { "authorId": 1, "content": "https://pris.ly/youtube", "createdAt": "2026-09-10 21:45:05.975729+06", "id": 2, "title": "Prisma on YouTube", "updatedAt": "2026-09-10 21:45:05.975+06" }
    ]
  },
  {
    "createdAt": "2026-09-10 21:45:05.980593+06",
    "email": "bob@prisma.io",
    "id": 2,
    "name": "Bob",
    "updatedAt": "2026-09-10 21:45:05.979+06",
    "username": null,
    "posts": [
      { "authorId": 2, "content": "https://www.twitter.com/prisma", "createdAt": "2026-09-10 21:45:05.984988+06", "id": 3, "title": "Follow Prisma on Twitter", "updatedAt": "2026-09-10 21:45:05.983+06" }
    ]
  }
]
```

The route handler is ordinary SolidStart code calling an ordinary Prisma ORM query; there is no framework adapter in between.

## 6. Render the users in a page [#6-render-the-users-in-a-page]

Replace `src/routes/index.tsx` with a page that loads the same query. The Prisma ORM 7 guide fetched the API route from the component with `fetch("http://localhost:3000/api/users")`. SolidStart renders pages on the server first, where a relative `fetch("/api/users")` fails with `Invalid URL` and an absolute one hard-codes your host, so the page calls the query through a server function instead. `query` from `@solidjs/router` caches and deduplicates it, and `createAsync` exposes the result to the component:

```typescript title="src/routes/index.tsx"
import { Title } from "@solidjs/meta";
import { createAsync, query } from "@solidjs/router";
import { ErrorBoundary, For, Suspense } from "solid-js";
import { db } from "~/prisma/db";

const getUsers = query(async () => {
  "use server";
  try {
    return await db.orm.public.User.include("posts").all();
  } catch (error) {
    console.error(error);
    throw new Error("Could not load users");
  }
}, "users");

export default function Home() {
  const users = createAsync(() => getUsers());

  return (
    <main>
      <Title>SolidStart + Prisma</Title>
      <h1>SolidStart + Prisma</h1>
      <ErrorBoundary fallback={<p>Error loading data</p>}>
        <Suspense fallback={<p>Loading...</p>}>
          <For each={users()}>
            {(user) => (
              <div>
                <h3>{user.name}</h3>
                <For each={user.posts}>{(post) => <p>{post.title}</p>}</For>
              </div>
            )}
          </For>
        </Suspense>
      </ErrorBoundary>
    </main>
  );
}
```

Three things to notice:

* `"use server"` keeps the Prisma query and the database connection on the server. The browser only receives the rows.
* The rows are typed by the contract: `user.name` and `user.posts` autocomplete without importing any generated types. The `User` and `Post` type imports from the Prisma ORM 7 guide are gone because `createAsync` infers the shape from the query.
* `<Suspense>` renders the loading state while the query runs and `<ErrorBoundary>` renders the error state if it throws. The `catch` block logs the real Prisma error on the server and throws a plain `Error` for the client; see the gotchas below for why.

Open [http://localhost:3000](http://localhost:3000) or request the page from the terminal:

```bash
curl http://localhost:3000/
```

The server-rendered HTML contains `<h3>Alice</h3>` and `<h3>Bob</h3>` with their post titles, streamed in after the `Loading...` fallback. Your SolidStart app now reads users and their posts from PostgreSQL through Prisma ORM, over both an API route and a server-rendered page.

## Common gotchas [#common-gotchas]

> [!WARNING]
> Don't call `db.runtime().close()` in API routes or server functions. The client in `src/prisma/db.ts` is constructed once and its connection pool is shared across requests; close it only in one-off scripts like the seed.

* **`orm init` installs with pnpm.** The SolidStart template ships a `pnpm-lock.yaml`, and `orm init` picks its package manager from the lockfile it finds. Delete that file before `npm install` (step 1), or use pnpm throughout.
* **`fetch("/api/users")` fails during server rendering.** Node.js has no origin to resolve a relative URL against, so the render throws `TypeError: Failed to parse URL from /api/users`. Load data through a `"use server"` function as in step 6; keep the API route for HTTP clients.
* **Rethrow a plain `Error` from server functions.** Prisma ORM throws structured errors that carry the SQL state, the failing statement, and a nested cause. When one of those crosses the server-to-browser boundary during server rendering, the page hangs on hydration instead of showing the `<ErrorBoundary>` fallback. Catching it and throwing `new Error("Could not load users")` keeps the fallback working and keeps database details out of the browser.
* **The first request after `npm run dev` can return a 503.** While Vite is still creating its server environment, a request may answer `Vite environment "ssr" is unavailable`. Wait a second and retry; every request after that succeeds.

## Prompt your coding agent [#prompt-your-coding-agent]

Run [`npx prisma@latest init`](https://www.prisma.io/docs/cli/init) once to install the [Prisma ORM skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) for your coding agent and keep them matching your installed packages:

  

#### bun

```bash
bunx --bun prisma@latest init
```

#### pnpm

```bash
pnpm dlx prisma@latest init
```

#### yarn

```bash
yarn dlx prisma@latest init
```

#### npm

```bash
npx prisma@latest init
```

```text no-copy
✔ Added "postinstall": "prisma skills sync || exit 0" to package.json.
⚠ prisma.config.ts already exists; left untouched.
✔ Synced 2 skills.

Skill            Package               Installed into
prisma-8         @prisma/orm-postgres  .claude/skills, .cursor/skills, .agents/skills, .devin/skills
```

Prompts that map to this guide:

* "Using the prisma-8 skill, add `GET /api/users/:id` that returns one user with posts or a 404."
* "Add a `POST /api/users` route that creates a user from the request body with `db.orm.public.User.create`."
* "Turn the user list into a form that creates a post through a SolidStart `action` and revalidates the `users` query."

## Next steps [#next-steps]

* Change the schema in `src/prisma/contract.prisma`, then run `npx prisma@latest contract emit` and `npx prisma@latest db update`.
* [Learn the fundamentals](https://www.prisma.io/docs/orm/fundamentals/reading-data): filtering, sorting, pagination, and writes.
* [Read the Prisma ORM overview](https://www.prisma.io/docs/orm) for the concepts behind contracts and typed queries.
* [SolidStart documentation](https://start.solidjs.com/) for routing, server functions, and deployment presets.

## Related pages

- [`Astro`](https://www.prisma.io/docs/guides/frameworks/astro): Set up Prisma ORM in an Astro app with create-prisma, from scaffold to rendered data, and deploy it to Prisma Compute.
- [`Elysia`](https://www.prisma.io/docs/guides/frameworks/elysia): Build an Elysia API on Prisma ORM with the elysia template and deploy it to Prisma Compute.
- [`Hono`](https://www.prisma.io/docs/guides/frameworks/hono): Build a Hono API on Prisma ORM with the hono template, add your own routes, and deploy it to Prisma Compute.
- [`NestJS`](https://www.prisma.io/docs/guides/frameworks/nestjs): Set up Prisma ORM in a NestJS app with create-prisma, from scaffold to seeded API to a live deploy on Prisma Compute.
- [`Next.js`](https://www.prisma.io/docs/guides/frameworks/nextjs): Set up Prisma ORM in a Next.js app with create-prisma, from scaffold to rendered data, and deploy it to Prisma Compute.