# 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
## 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.6.3
✔ 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({
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 db init
```
#### pnpm
```bash
pnpm prisma db init
```
#### yarn
```bash
yarn prisma db init
```
#### npm
```bash
npx prisma 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 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e
```
`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 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.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.1 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 (
SolidStart + Prisma
SolidStart + Prisma
Error loading data
}>
Loading...}>
{(user) => (
{user.name}
{(post) => {post.title}
}
)}
);
}
```
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.
* `` renders the loading state while the query runs and `` 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 `Alice
` and `Bob
` 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.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 `` 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.
## 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 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.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 `` for loading and `` 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.
```
The prompt above runs [`npx prisma@latest init`](https://www.prisma.io/docs/cli/init) in its first step. If you followed the steps by hand, run it once yourself: it installs the [Prisma ORM skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) for your coding agent and keeps 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 3 skills.
project: ~/my-solid-prisma-app
check: enabled
Skill Package Version Installed into
prisma-8 @prisma/orm-postgres 8.0.0-rc.17 .claude/skills, .cursor/skills, .agents/skills, .devin/skills
prisma-composer-core-concepts @prisma/composer 0.25.0 .claude/skills, .cursor/skills, .agents/skills, .devin/skills
prisma-platform-core-concepts prisma 8.0.0-rc.21 .claude/skills, .cursor/skills, .agents/skills, .devin/skills
⚠ [INIT.CONFIG_KEPT] prisma.config.ts already exists, so init left it alone instead of writing the skills section.
→ Add skills: { agents: ["claude", "cursor", "agents", "devin"] } to the object passed to definePrismaConfig in prisma.config.ts.
```
Prompts you can give your agent after 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 contract emit` and `npx prisma db update`. SolidStart builds on Vite, so the [Vite plugin](https://www.prisma.io/docs/cli/contract-emit#run-it-automatically) can run `contract emit` for you whenever the contract changes while the dev server runs.
* [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.