apps/docs/content/docs/guides/frameworks/elysia.mdx
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.
Create a new Elysia project using the Bun scaffolding command:
bun create elysia elysia-prisma
Navigate to the project directory:
cd elysia-prisma
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 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.
:::
Once installed, initialize Prisma in your project:
bunx --bun 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:
prisma/ directory with a schema.prisma fileprisma.config.ts file for configuring Prisma.env file with a DATABASE_URL already setIn 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 { // [!code ++]
id Int @id @default(autoincrement()) // [!code ++]
title String // [!code ++]
completed Boolean @default(false) // [!code ++]
createdAt DateTime @default(now()) // [!code ++]
updatedAt DateTime @updatedAt // [!code ++]
} // [!code ++]
This matches the Prisma Elysia example: it generates Prisma Client to src/generated/prisma and Prismabox TypeBox schemas to src/generated/prismabox.
src/generated/prismabox/Todo.ts (and one per model) with TodoPlain, TodoPlainInputCreate, etc.Run the following commands to create the database tables and generate the Prisma Client:
bunx --bun prisma migrate dev --name init
bunx --bun prisma generate
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 seed
And open Prisma Studio to inspect your data:
bunx --bun prisma studio
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 });
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 todoPrismabox generates the TodoPlain/TodoPlainInput* TypeBox schemas so responses and request bodies are validated and typed.
Start your Elysia server:
bun run dev
You should see 🦊 Elysia is running at localhost:3000 in the console.
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 | jq
You're done! You've created an Elysia app with Prisma that's connected to a Prisma Postgres database.
Now that you have a working Elysia app connected to a Prisma Postgres database, you can: