Skip to main content
Prisma Documentation Docs

Search documentation

Type to search this documentation.

On this pageOverview

Elysia

Elysia is a web framework for Bun with end-to-end type safety. Its route handlers are where you call Prisma ORM to read and write a Prisma Postgres database, and Prismabox keeps its request and response schemas in sync with your Prisma models.

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.

Run one command to scaffold an Elysia project with Prisma ORM and Prisma Postgres ready to go:

title="bun"
bunx create-prisma@stable --template elysia
pnpm
pnpm create prisma@stable --template elysia
yarn
yarn create prisma@stable --template elysia
npm
npm create prisma@stable -- --template elysia

Or follow the steps below to set it up manually.

  • Bun installed on your system

Create a new Elysia project:

title="bun"
bunx create-elysia elysia-prisma
pnpm
pnpm create elysia elysia-prisma
yarn
yarn create elysia elysia-prisma
npm
npm create elysia@latest 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 prisma@prev bun-types --dev

bun add @prisma/client@7 @prisma/adapter-pg pg prismabox
Bash
pnpm add prisma@prev bun-types --save-dev
pnpm add @prisma/client@7 @prisma/adapter-pg pg prismabox
Bash
yarn add prisma@prev bun-types --dev
yarn add @prisma/client@7 @prisma/adapter-pg pg prismabox
Bash
npm install prisma@prev bun-types --save-dev
npm install @prisma/client@7 @prisma/adapter-pg pg prismabox

[!NOTE] 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:

Once installed, initialize Prisma in your project:

bunx --bun prisma init --output ../src/generated/prisma
Bash
pnpm prisma init --output ../src/generated/prisma
Bash
yarn prisma init --output ../src/generated/prisma
Bash
npx prisma init --output ../src/generated/prisma

[!NOTE] prisma init creates the Prisma scaffolding and a local DATABASE_URL. In the next step, you will create a Prisma Postgres database and replace that value with a direct postgres://... connection string.

This will create:

  • A prisma/ directory with a schema.prisma file
  • A prisma.config.ts file for configuring Prisma
  • A .env file with a local DATABASE_URL already set

Create a Prisma Postgres database and replace the generated DATABASE_URL in your .env file with the postgres://... connection string from the CLI output:

This will create:

  • A prisma/ directory with a schema.prisma file
  • A prisma.config.ts file for configuring Prisma
  • A .env file with a local DATABASE_URL already set

Create a Prisma Postgres database and replace the generated DATABASE_URL in your .env file with the postgres://... connection string from the CLI output:

title="bun"
bunx create-db
pnpm
pnpm dlx create-db
yarn
yarn dlx create-db
npm
npx create-db

In the prisma/schema.prisma file, mirror the example app structure with Prismabox TypeBox generators and a Todo model:

title="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"

}

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.

  • 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).

Run the following commands to create the database tables and generate the Prisma Client:

title="bun"
bunx prisma migrate dev --name init

bunx prisma generate
pnpm
pnpm prisma migrate dev --name init
pnpm prisma generate
yarn
yarn prisma migrate dev --name init
yarn prisma generate
npm
npx prisma migrate dev --name init
npx 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:

title="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:

title="bun"
bunx prisma db seed
pnpm
pnpm prisma db seed
yarn
yarn prisma db seed
npm
npx prisma db seed

And open Prisma Studio to inspect your data:

title="bun"
bunx prisma studio
pnpm
pnpm prisma studio
yarn
yarn prisma studio
npm
npx 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:

title="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 });

Update your src/index.ts file to match the Prisma Elysia example, including Prismabox-generated validation types:

title="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.

Start your Elysia server:

title="bun"
bun run dev
pnpm
pnpm run dev
yarn
yarn dev
npm
npm 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

Your Elysia app now serves a todo API backed by a Prisma Postgres database.

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
  • Use Eden for end-to-end type-safe API calls
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu