Skip to content

FraiseQL vs Prisma

Prisma is a popular ORM. FraiseQL is a GraphQL framework backed by a Rust binary. Here’s why you might choose one over the other.

Prisma is an ORM that generates a type-safe database client. You still need to build your API layer (Express, Fastify, Apollo, etc.).

FraiseQL generates the entire GraphQL API, including the database access layer — backed by hand-written PostgreSQL views (v_*) for reads and PostgreSQL functions (fn_*) for writes.

With Prisma:
Your Code -> Prisma Client -> Database
(You build the API layer)
With FraiseQL:
Client -> GraphQL -> FraiseQL Rust Binary -> PostgreSQL Views/Functions
(API layer is built-in)
AspectFraiseQLPrisma
What it isGraphQL framework (Rust binary)ORM / Database client
OutputComplete GraphQL APIDatabase client library
API layerIncludedBuild yourself
Schema sourcePython, TypeScript, Go decoratorsPrisma Schema Language
ReadsPre-compiled PostgreSQL views (v_*)Generated SQL via ORM
WritesPostgreSQL functions (fn_*)Generated SQL via ORM
N+1 handlingEliminated by design (SQL JOINs in views)include option (manual)
Query languageGraphQLPrisma Client API
ConfigurationTOML.prisma files
Database supportPostgreSQL-firstPostgreSQL, MySQL, SQLite, SQL Server, MongoDB
RuntimeRust binaryNode.js
// 1. Define Prisma schema
// schema.prisma
model User {
id String @id @default(uuid())
name String
email String @unique
posts Post[]
}
model Post {
id String @id @default(uuid())
title String
author User @relation(fields: [authorId], references: [id])
authorId String
}
// 2. Generate client
// $ prisma generate
// 3. Build your API layer (Express, Fastify, Apollo, etc.)
app.get('/users', async (req, res) => {
const users = await prisma.user.findMany({
include: { posts: true }
});
res.json(users);
});
app.get('/users/:id', async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: req.params.id },
include: { posts: true }
});
res.json(user);
});
app.post('/users', async (req, res) => {
const user = await prisma.user.create({
data: req.body
});
res.json(user);
});
// ... 50+ more endpoints
// Without include: N+1 problem
const users = await prisma.user.findMany();
for (const user of users) {
user.posts = await prisma.post.findMany({
where: { authorId: user.id }
}); // N queries!
}
// With include: You must remember to add it
const users = await prisma.user.findMany({
include: { posts: true }
});

If you forget include, you get N+1. If you include too much, you fetch unnecessary data.

# This query:
query {
users {
name
posts { title }
}
}

Because the v_user view already JOINs tb_post, this resolves in a single SQL execution. You cannot accidentally create N+1 queries — the SQL is pre-compiled into the view.

schema.prisma
model User {
id String @id @default(uuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
@@index([email])
@@map("users")
}
model Post {
id String @id @default(uuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
@@map("posts")
}
  • Custom DSL to learn
  • Good tooling (VS Code extension)
  • Limited to Prisma’s feature set

Prisma doesn’t include validation — you must use external libraries:

// Prisma + Zod for validation
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
age: z.number().min(0).max(150)
});
app.post('/users', async (req, res) => {
const validated = createUserSchema.parse(req.body); // Runtime validation
const user = await prisma.user.create({
data: validated
});
res.json(user);
});
  • External dependencies — Zod, Joi, Yup, etc.
  • Runtime validation — Happens during request handling
  • Your responsibility — Must validate before database calls
  • Database risk — Invalid data if validation is missed

FraiseQL includes 13 validators enforced at compile-time:

[fraiseql.validation]
email = { pattern = "^[^@]+@[^@]+\\.[^@]+$" }
age = { range = { min = 0, max = 150 } }
name = { length = { min = 1 } }
  • 13 built-in validators — No external libraries needed
    • Standard: required, pattern, length, range, enum, checksum
    • Cross-field: comparison operators, conditionals
    • Mutual exclusivity: OneOf, AnyOf, ConditionalRequired, RequiredIfAbsent
  • Compile-time enforcement — Invalid schemas are impossible at runtime
  • Zero dependencies — Everything built-in
  • Database protection — Invalid data never reaches the database
AspectFraiseQLPrisma
Built-in validators13 rules0 (requires external libs)
External dependenciesNoneZod, Joi, Yup, etc.
Compile-time enforcementYesNo
Runtime validation overheadNonePer-request
Learning curveLowMedium (varies by library)
Database protectionGuaranteedDepends on implementation

Prisma is a better choice when:

  • You’re building a REST API — Prisma doesn’t prescribe your API layer
  • You need fine-grained control — Build exactly the endpoints you want
  • You want GraphQL with custom resolvers — Prisma + Apollo/Yoga/etc.
  • You need MongoDB support — FraiseQL is PostgreSQL-first
  • Your API has complex business logic — More control over request handling
  • You prefer auto-generated SQL — No SQL views to write upfront

FraiseQL is a better choice when:

  • You want a GraphQL API — It’s built-in, no extra layer to wire up
  • You want less application code — Schema + SQL views, no API layer
  • You need guaranteed N+1 prevention — By design, not by discipline
  • You prefer compiled output — Rust binary with predictable performance
  • You want simple configuration — TOML over multiple files
  • You trust your SQL — Write the queries you know are optimal
  1. Create a Prisma project:

    Terminal window
    npm init -y
    npm install @prisma/client prisma
    npx prisma init

    Edit schema.prisma:

    generator client {
    provider = "prisma-client-js"
    }
    datasource db {
    provider = "postgresql"
    url = env("DATABASE_URL")
    }
    model User {
    id String @id @default(uuid())
    email String @unique
    name String
    posts Post[]
    }
    model Post {
    id String @id @default(uuid())
    title String
    author User @relation(fields: [authorId], references: [id])
    authorId String
    }
    Terminal window
    npx prisma migrate dev --name init
    npx prisma generate
  2. Build the API layer manually:

    // server.js - You must build this yourself
    const { PrismaClient } = require('@prisma/client');
    const express = require('express');
    const prisma = new PrismaClient();
    const app = express();
    app.get('/users', async (req, res) => {
    const users = await prisma.user.findMany({
    include: { posts: true } // Remember this or get N+1!
    });
    res.json(users);
    });
    app.get('/users/:id', async (req, res) => {
    const user = await prisma.user.findUnique({
    where: { id: req.params.id },
    include: { posts: true }
    });
    res.json(user);
    });
    app.listen(3000);

    Test: curl http://localhost:3000/users

  3. Now try the same with FraiseQL:

    schema.py
    import fraiseql
    @fraiseql.type
    class User:
    id: str
    email: str
    name: str
    posts: list['Post']
    @fraiseql.type
    class Post:
    id: str
    title: str
    author: User
    -- Create the SQL view (write once)
    CREATE VIEW v_user AS
    SELECT u.id, u.email, u.name,
    COALESCE(json_agg(p.*) FILTER (WHERE p.id IS NOT NULL), '[]') AS posts
    FROM tb_user u
    LEFT JOIN tb_post p ON p.user_id = u.id
    GROUP BY u.id;
    Terminal window
    fraiseql run
    # GraphQL API is ready - no REST endpoints to write
    curl -X POST http://localhost:8080/graphql \
    -H "Content-Type: application/json" \
    -d '{"query": "{ users { name posts { title } } }"}'
  4. Compare N+1 handling:

    With Prisma, you must remember include:

    // Without include: N+1 queries!
    const users = await prisma.user.findMany();
    for (const user of users) {
    user.posts = await prisma.post.findMany({ where: { authorId: user.id } });
    }

    With FraiseQL, N+1 is impossible — the view already JOINs the data.

  5. Compare deployment complexity:

    Prisma deployment:

    • Node.js runtime
    • Express/Fastify server
    • Prisma Client
    • Database connection pool management
    • Your custom API code

    FraiseQL deployment:

    • Single Rust binary
    • Pre-built SQL views
    • That’s it

You can use Prisma’s migration tooling alongside FraiseQL’s API server:

Terminal window
# Use Prisma Migrate for database schema management
prisma migrate dev
# Use FraiseQL to serve the GraphQL API
fraiseql run

Or use FraiseQL for your customer-facing GraphQL API and Prisma for admin/internal tools.

  1. Convert Schema

    model User {
    id String @id @default(uuid())
    name String
    posts Post[]
    }
  2. Write SQL Views to Replace the API Layer

    Your Express/Fastify routes are replaced by the GraphQL API. Each query maps to a PostgreSQL view that you write and control.

    CREATE VIEW v_user AS
    SELECT u.id, u.name,
    COALESCE(json_agg(p.*) FILTER (WHERE p.id IS NOT NULL), '[]') AS posts
    FROM tb_user u
    LEFT JOIN tb_post p ON p.user_id = u.id
    GROUP BY u.id;
  3. Move Business Logic to PostgreSQL Functions

    Complex route handlers become PostgreSQL functions (fn_*), or use FraiseQL’s observer pattern to react to data changes:

    @observer(
    entity="Order",
    event="UPDATE",
    condition="status = 'pending'",
    actions=[
    webhook("https://payments.internal/process",
    body={"order_id": "{id}", "amount": "{total}"}),
    ],
    )
    def on_order_pending():
    """Process payment when order becomes pending."""
    pass
ChooseWhen
PrismaYou want an ORM, building REST, need MongoDB, want full control over application code
FraiseQLYou want GraphQL, less application code, guaranteed N+1 prevention, Rust-level performance

Prisma is a database client. FraiseQL is a complete GraphQL API server.

Prisma Migration Guide

Step-by-step guide to migrate from Prisma to FraiseQL.

Read guide

Getting Started

Learn FraiseQL fundamentals from scratch.

Read guide

Schema Concepts

Understand SQL views, types, and decorators in depth.

Read guide