diff --git a/cSpell.json b/cSpell.json index eec3816dd7..c102ce6856 100644 --- a/cSpell.json +++ b/cSpell.json @@ -52,6 +52,7 @@ "Openform", "Overfetching", "pgbouncer", + "elysia", "planetscale", "pooler", "poolers", diff --git a/content/800-guides/440-elysia.mdx b/content/800-guides/440-elysia.mdx new file mode 100644 index 0000000000..94a0a2197d --- /dev/null +++ b/content/800-guides/440-elysia.mdx @@ -0,0 +1,453 @@ +--- +title: 'How to use Prisma with Elysia' +metaTitle: 'How to use Prisma ORM and Prisma Postgres with Elysia' +description: 'Learn how to use Prisma ORM in an Elysia app' +sidebar_label: 'Elysia' +image: '/img/guides/prisma-elysia-cover.png' +completion_time: '15 min' +community_section: true +--- + +## Introduction + +[Elysia](https://elysiajs.com/) is an ergonomic web framework for building high-performance backend servers with Bun. It offers end-to-end type safety, an expressive API, and exceptional performance. Combined with Prisma ORM and [Prisma Postgres](https://www.prisma.io/postgres), you get a fast, type-safe backend stack. + +In this guide, you'll learn to integrate Prisma ORM with a Prisma Postgres database in an Elysia application. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/elysia). + +## Prerequisites + +- [Bun](https://bun.sh/docs/installation) installed on your system + +## 1. Set up your project + +Create a new Elysia project using the Bun scaffolding command: + +```terminal +bun create elysia elysia-prisma +``` + +Navigate to the project directory: + +```terminal +cd elysia-prisma +``` + +## 2. Install and configure Prisma + +### 2.1. Install dependencies + +Install the required Prisma packages, database adapter, and Prismabox (for generated TypeBox schemas): + +```terminal +bun add -d prisma bun-types +bun add @prisma/client @prisma/adapter-pg pg prismabox +``` + +:::info + +If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](/orm/overview/databases/database-drivers). + +::: + +Once installed, initialize Prisma in your project: + +```terminal +bunx prisma init --db --output ../src/generated/prisma +``` + +:::info +You'll need to answer a few questions while setting up your Prisma Postgres database. Select the region closest to your location and a memorable name for your database like "My Elysia Project" +::: + +This will create: + +- A `prisma/` directory with a `schema.prisma` file +- A `prisma.config.ts` file for configuring Prisma +- A `.env` file with a `DATABASE_URL` already set + +### 2.2. Define your Prisma Schema + +In the `prisma/schema.prisma` file, mirror the example app structure with Prismabox TypeBox generators and a `Todo` model: + +```prisma file=prisma/schema.prisma +generator client { + provider = "prisma-client" + output = "../src/generated/prisma" +} + +generator prismabox { + provider = "prismabox" + typeboxImportDependencyName = "elysia" + typeboxImportVariableName = "t" + inputModel = true + output = "../src/generated/prismabox" +} + +datasource db { + provider = "postgresql" +} + +//add-start +model Todo { + id Int @id @default(autoincrement()) + title String + completed Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} +//add-end +``` + +This matches the Prisma Elysia example: it generates Prisma Client to `src/generated/prisma` and Prismabox TypeBox schemas to `src/generated/prismabox`. + +#### What is Prismabox? + +- A Prisma generator that reads your Prisma schema and emits Elysia-friendly TypeBox models. +- Generates files like `src/generated/prismabox/Todo.ts` (and one per model) with `TodoPlain`, `TodoPlainInputCreate`, etc. +- Use those generated models in routes to validate requests/responses and keep Elysia types in sync with your Prisma schema (also useful for OpenAPI/Eden). + +### 2.3. Run migrations and generate Prisma Client + +Run the following commands to create the database tables and generate the Prisma Client: + +```terminal +bunx prisma migrate dev --name init +bunx prisma generate +``` + +### 2.4. Seed the database + +Add some seed data to populate the database with sample todos (mirrors the example repo). + +Create a new file called `seed.ts` in the `prisma/` directory: + +```typescript file=prisma/seed.ts +import { PrismaClient } from "../src/generated/prisma/client.js"; +import { PrismaPg } from "@prisma/adapter-pg"; + +if (!process.env.DATABASE_URL) { + throw new Error("DATABASE_URL is not set"); +} + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); +const prisma = new PrismaClient({ adapter }); + +const todoData = [ + { title: "Learn Elysia" }, + { title: "Learn Prisma" }, + { title: "Build something awesome", completed: true }, +]; + +async function main() { + console.log("Start seeding..."); + for (const todo of todoData) { + const created = await prisma.todo.create({ + data: todo, + }); + console.log(`Created todo with id: ${created.id}`); + } + console.log("Seeding finished."); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); +``` + +Run the seed script: + +```terminal +bunx prisma db seed +``` + +And open Prisma Studio to inspect your data: + +```terminal +bunx prisma studio +``` + +## 3. Integrate Prisma into Elysia + +### 3.1. Create a Prisma Client instance + +Inside the `src/` directory, create a `lib` directory with a `prisma.ts` file. This file will create and export your Prisma Client instance and add the following code: + +```typescript file=src/lib/prisma.ts +import { PrismaClient } from "../generated/prisma/client.js"; +import { PrismaPg } from "@prisma/adapter-pg"; + +const databaseUrl = process.env.DATABASE_URL; + +if (!databaseUrl) { + throw new Error("DATABASE_URL is not set"); +} + +const adapter = new PrismaPg({ connectionString: databaseUrl }); +export const prisma = new PrismaClient({ adapter }); +``` +### 3.2. Create API routes + +Update your `src/index.ts` file to match the Prisma Elysia example, including Prismabox-generated validation types: + +```typescript file=src/index.ts +import { Elysia, t } from "elysia"; +import { prisma } from "./lib/prisma"; +import { + TodoPlain, + TodoPlainInputCreate, + TodoPlainInputUpdate, +} from "./generated/prismabox/Todo"; + +const app = new Elysia() + // Health check + .get("/", () => { + return { message: "Hello Elysia with Prisma!" }; + }) + + // Get all todos + .get( + "/todos", + async () => { + const todos = await prisma.todo.findMany({ + orderBy: { createdAt: "desc" }, + }); + return todos; + }, + { + response: t.Array(TodoPlain), + } + ) + + // Get a single todo by ID + .get( + "/todos/:id", + async ({ params, set }) => { + const id = Number(params.id); + const todo = await prisma.todo.findUnique({ + where: { id }, + }); + + if (!todo) { + set.status = 404; + return { error: "Todo not found" }; + } + + return todo; + }, + { + params: t.Object({ + id: t.Numeric(), + }), + response: { + 200: TodoPlain, + 404: t.Object({ + error: t.String(), + }), + }, + } + ) + + // Create a new todo + .post( + "/todos", + async ({ body }) => { + const todo = await prisma.todo.create({ + data: { + title: body.title, + }, + }); + return todo; + }, + { + body: TodoPlainInputCreate, + response: TodoPlain, + } + ) + + // Update a todo + .put( + "/todos/:id", + async ({ params, body, set }) => { + const id = Number(params.id); + + try { + const todo = await prisma.todo.update({ + where: { id }, + data: { + title: body.title, + completed: body.completed, + }, + }); + return todo; + } catch { + set.status = 404; + return { error: "Todo not found" }; + } + }, + { + params: t.Object({ + id: t.Numeric(), + }), + body: TodoPlainInputUpdate, + response: { + 200: TodoPlain, + 404: t.Object({ + error: t.String(), + }), + }, + } + ) + + // Toggle todo completion + .patch( + "/todos/:id/toggle", + async ({ params, set }) => { + const id = Number(params.id); + + try { + const todo = await prisma.todo.findUnique({ + where: { id }, + }); + + if (!todo) { + set.status = 404; + return { error: "Todo not found" }; + } + + const updated = await prisma.todo.update({ + where: { id }, + data: { completed: !todo.completed }, + }); + + return updated; + } catch { + set.status = 404; + return { error: "Todo not found" }; + } + }, + { + params: t.Object({ + id: t.Numeric(), + }), + response: { + 200: TodoPlain, + 404: t.Object({ + error: t.String(), + }), + }, + } + ) + + // Delete a todo + .delete( + "/todos/:id", + async ({ params, set }) => { + const id = Number(params.id); + + try { + const todo = await prisma.todo.delete({ + where: { id }, + }); + return todo; + } catch { + set.status = 404; + return { error: "Todo not found" }; + } + }, + { + params: t.Object({ + id: t.Numeric(), + }), + response: { + 200: TodoPlain, + 404: t.Object({ + error: t.String(), + }), + }, + } + ) + + .listen(3000); + +console.log( + `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` +); +``` + +This creates the same endpoints as the official example: + +- `GET /` - Health check +- `GET /todos` - Fetch all todos (newest first) +- `GET /todos/:id` - Fetch a single todo +- `POST /todos` - Create a todo +- `PUT /todos/:id` - Update a todo +- `PATCH /todos/:id/toggle` - Toggle completion +- `DELETE /todos/:id` - Delete a todo + +Prismabox generates the `TodoPlain`/`TodoPlainInput*` TypeBox schemas so responses and request bodies are validated and typed. + +### 3.3. Run the application + +Start your Elysia server: + +```terminal +bun run dev +``` + +You should see `🦊 Elysia is running at localhost:3000` in the console. + +### 3.4. Test the API + +Test the endpoints using `curl`: + +```terminal +# Health check +curl http://localhost:3000/ | jq + +# Get all todos +curl http://localhost:3000/todos | jq + +# Get a single todo +curl http://localhost:3000/todos/1 | jq + +# Create a new todo +curl -X POST http://localhost:3000/todos \ + -H "Content-Type: application/json" \ + -d '{"title": "Ship the Prisma + Elysia guide"}' | jq + +# Toggle completion +curl -X PATCH http://localhost:3000/todos/1/toggle | jq + +# Update a todo +curl -X PUT http://localhost:3000/todos/1 \ + -H "Content-Type: application/json" \ + -d '{"title": "Updated title", "completed": true}' | jq + +# Delete a todo +curl -X DELETE http://localhost:3000/todos/1 | jq +``` + +You're done! You've created an Elysia app with Prisma that's connected to a Prisma Postgres database. + +## Next steps + +Now that you have a working Elysia app connected to a Prisma Postgres database, you can: + +- Extend your Prisma schema with more models and relationships +- Add update and delete endpoints +- Explore authentication with [Elysia plugins](https://elysiajs.com/plugins/overview.html) +- Enable query caching with [Prisma Postgres](/postgres/database/caching) for better performance +- Use [Eden](https://elysiajs.com/eden/overview.html) for end-to-end type-safe API calls + +### More info + +- [Prisma Documentation](/orm/overview/introduction) +- [Elysia Documentation](https://elysiajs.com/) +- [Elysia with Prisma Guide](https://elysiajs.com/integrations/prisma.html) + diff --git a/sidebars.ts b/sidebars.ts index 16be7bfd12..9590190de0 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -365,6 +365,7 @@ const sidebars: SidebarsConfig = { "guides/sveltekit", "guides/astro", "guides/hono", + "guides/elysia", "guides/solid-start", "guides/react-router-7", "guides/tanstack-start",