Elysia
Learn how to use Prisma ORM in an Elysia app
Introduction
Elysia 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, 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.
Prerequisites
- Bun installed on your system
1. Set up your project
Create a new Elysia project using the Bun scaffolding command:
bun create elysia elysia-prismaNavigate to the project directory:
cd elysia-prisma2. Install and configure Prisma
2.1. Install dependencies
Install the required Prisma packages, database adapter, and Prismabox (for generated TypeBox schemas):
bun add -d prisma bun-types
bun add @prisma/client @prisma/adapter-pg pg prismaboxIf 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.
Once installed, initialize Prisma in your project:
bunx --bun prisma init --db --output ../src/generated/prismaYou'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 aschema.prismafile - A
prisma.config.tsfile for configuring Prisma - A
.envfile with aDATABASE_URLalready 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:
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"
}
model Todo {
id Int @id @default(autoincrement())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
} 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) withTodoPlain,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:
bunx --bun prisma migrate dev --name init
bunx --bun prisma generate2.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:
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:
bunx --bun prisma db seedAnd open Prisma Studio to inspect your data:
bunx --bun prisma studio3. 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:
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:
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 checkGET /todos- Fetch all todos (newest first)GET /todos/:id- Fetch a single todoPOST /todos- Create a todoPUT /todos/:id- Update a todoPATCH /todos/:id/toggle- Toggle completionDELETE /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:
bun run devYou should see 🦊 Elysia is running at localhost:3000 in the console.
3.4. Test the API
Test the endpoints using curl:
# 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 | jqYou'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
- Enable query caching with Prisma Postgres for better performance
- Use Eden for end-to-end type-safe API calls