The backend uses both Prisma and Kysely. They solve different problems and work together.
Prisma owns the database schema. You define models in prisma/schema.prisma, and Prisma handles:
prisma migrate devprisma db seedmodel Project {
id String @id @default(cuid())
name String
organizationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
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.
| Task | Use |
|---|---|
| Define schema, add tables | Prisma (schema.prisma) |
| Run migrations | Prisma (prisma migrate dev) |
| Insert records | Prisma client |
| Read data (queries) | Kysely |
| Complex writes (transactions) | Kysely |
| Seed data | Prisma |
features/<domain>/db/queries/features/<domain>/db/mutations/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
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 });