Custom Prisma Query Options
Arkos automatically generates API endpoints based on your Prisma models while still giving you full control over the underlying database queries. The PrismaQueryOptions
configuration allows you to define default query parameters for each operation type, ensuring consistent data access patterns while maintaining flexibility.
How Arkos Handles Prisma Query Customization
The prismaQueryOptions
configuration lets you set default options for all Prisma operations, which can be overridden by request query parameters when needed.
Key Benefits
- Consistent Data Access: Apply standard includes, selects, and filters across all endpoints
- Security By Default: Exclude sensitive fields automatically
- Performance Optimization: Pre-configure relation loading for better performance
- Operation-Specific Settings: Apply different configurations for reads vs writes
Configuration Structure
For it to work you must delcares create a file src/modules/model-name/model-name.prisma-query-options.ts
so that Arkos can find it and make usage of it.
Arkos supports customizing options for all standard Prisma operations:
// src/modules/author/author.prisma-query-options.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
const authorPrismaQueryOptions: PrismaQueryOptions<Prisma.ModelDelegate> = {
// Global options applied to all operations
queryOptions: { ... },
// Operation-specific options
findOne: { ... },
findMany: { ... },
createOne: { ... },
updateOne: { ... },
deleteOne: { ... },
createMany: { ... },
updateMany: { ... },
deleteMany: { ... }
};
export default authorPrismaQueryOptions;
Is very important to follow the file name conventions and folder structure stated above in order for this to work, you can also read more about Arkos overall project structure clicking here
.
Available Query Options
The PrismaQueryOptions
type supports all standard Prisma query parameters:
type PrismaQueryOptions<T> = {
// Global options applied to all operations
queryOptions?: Prisma.Args<T, "findMany">;
// Read operations
findOne?: Partial<Prisma.Args<T, "findUnique">>;
findMany?: Partial<Prisma.Args<T, "findMany">>;
// Write operations
createOne?: Partial<Prisma.Args<T, "create">>;
updateOne?: Partial<Prisma.Args<T, "update">>;
deleteOne?: Partial<Prisma.Args<T, "delete">>;
// Bulk operations
createMany?: Partial<Prisma.Args<T, "createMany">>;
updateMany?: Partial<Prisma.Args<T, "updateMany">>;
deleteMany?: Partial<Prisma.Args<T, "deleteMany">>;
};
Each operation type supports the full range of Prisma options including:
select
: Specify which fields to include in the responseinclude
: Configure which relations to loadwhere
: Add default filtersorderBy
: Set default sortingtake
&skip
: Configure pagination defaultsdistinct
: Select distinct records
Configuration Location
Place your Prisma query options in a file following this naming convention:
src/modules/[model-name]/[model-name].prisma-query-options.ts
Arkos will automatically discover and apply these options when generating your API endpoints.
Priority and Merging Behavior
Arkos follows a clear precedence order when applying query options:
- Request query parameters (highest priority)
- Operation-specific options (e.g.,
findMany
,createOne
) - Global query options (lowest priority)
Options are intelligently merged at each level, with object properties being deep-merged rather than replaced.
Request query parameters always take precedence over configured defaults, allowing API consumers to override your defaults when needed, as mentioned they are deep-merged rather than replaced.
You can read more about how Arkos allows developers to handle request query parameters clicking here
Usage Examples
Basic Example: Excluding Sensitive Data
// src/modules/user/user.prisma-query-options.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
const userPrismaQueryOptions: PrismaQueryOptions<Prisma.UserDelegate> = {
queryOptions: {
select: {
id: true,
name: true,
email: true,
createdAt: true,
updatedAt: true,
// Explicitly exclude sensitive fields
password: false,
resetToken: false,
twoFactorSecret: false,
},
},
};
export default userPrismaQueryOptions;
Advanced Example: Different Behaviors Per Operation
// src/modules/post/post.prisma-query-options.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
const postPrismaQueryOptions: PrismaQueryOptions<Prisma.PostDelegate> = {
// Global defaults for all operations
queryOptions: {
where: {
published: true, // Default to only published posts
},
orderBy: {
createdAt: "desc", // Newest first
},
},
// List view shows less data
findMany: {
select: {
id: true,
title: true,
excerpt: true,
author: {
select: {
id: true,
name: true,
},
},
createdAt: true,
tags: true,
},
take: 10, // Default page size
},
// Detailed view includes comments and full content
findOne: {
include: {
author: {
select: {
id: true,
name: true,
bio: true,
},
},
comments: {
where: {
approved: true,
},
orderBy: {
createdAt: "asc",
},
include: {
author: {
select: {
id: true,
name: true,
},
},
},
},
tags: true,
},
},
// Creating/updating includes validation
createOne: {
include: {
author: true,
tags: true,
},
},
// Admin operations can see unpublished posts too
deleteOne: {
where: {
// No default filters, allowing deletion of unpublished posts
},
},
};
export default postPrismaQueryOptions;
Complex Relations: User Model with Role Management
// src/modules/user/user.prisma-query-options.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
const userPrismaQueryOptions: PrismaQueryOptions<Prisma.UserDelegate> = {
queryOptions: {
// Base configuration for all operations
select: {
id: true,
name: true,
email: true,
createdAt: true,
updatedAt: true,
// Sensitive data excluded by default
password: false,
isActive: true,
},
},
// For listing users, include their roles
findMany: {
include: {
roles: {
select: {
role: {
select: {
id: true,
name: true,
},
},
},
},
},
where: {
isActive: true,
deletedAt: null,
},
orderBy: {
name: "asc",
},
},
// User profile view includes more details
findOne: {
include: {
roles: {
select: {
role: true,
},
},
posts: {
where: {
published: true,
},
select: {
id: true,
title: true,
createdAt: true,
},
orderBy: {
createdAt: "desc",
},
take: 5,
},
},
},
// When creating users, auto-include created roles
createOne: {
include: {
roles: true,
},
},
};
export default userPrismaQueryOptions;
Request Query Parameters Override
When API clients send query parameters, these override your configured defaults. For example:
API Request:
GET /api/users?select=id,name,email&include=posts&where[isActive]=true
This would override any conflicting select
, include
, or where
parameters in your configuration, while keeping non-conflicting options intact.
Best Practices
- Security First: Always exclude sensitive fields in your
queryOptions
global configuration - Pagination Defaults: Set reasonable
take
limits to prevent performance issues - Deep Relations: Be cautious with deeply nested
include
statements - Documentation: Document your default behavior for API consumers
- Performance: Use
select
instead ofinclude
when possible to minimize data transfer