Prisma and Kysely

The backend uses both Prisma and Kysely. They solve different problems and work together.

Prisma: schema and migrations

Prisma owns the database schema. You define models in prisma/schema.prisma, and Prisma handles:

model Project {
  id             String   @id @default(cuid())
  name           String
  organizationId String
  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt
}

Kysely: typed queries

Kysely is the query builder. It uses the types generated by Prisma for compile-time checked SQL:

import { postgres } from "@backend/context";

export const getProjectById = async ({ projectId }: { projectId: string }) => {
  return postgres.qb
    .selectFrom("project")
    .selectAll()
    .where("id", "=", projectId)
    .executeTakeFirst();
};

Every column name, table name, and operator is type-checked. Typos break at compile time.

When to use which

TaskUse
Define schema, add tablesPrisma (schema.prisma)
Run migrationsPrisma (prisma migrate dev)
Insert recordsPrisma client
Read data (queries)Kysely
Complex writes (transactions)Kysely
Seed dataPrisma

File conventions

features/projects/
  db/
    queries/
      get-by-id.query.ts
      get-many.query.ts
    mutations/
      create-project.mutation.ts
      update-project.mutation.ts
      delete-project.mutation.ts

Pagination

Use the shared getPaginatedQuery helper for paginated reads:

import { getPaginatedQuery } from "@backend/utils/database/pagination";

const query = postgres.qb.selectFrom("project").selectAll();
return getPaginatedQuery({ query, limit, offset });