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 create a file src/modules/model-name/model-name.query.ts so that Arkos can find it and make usage of it.
Since v1.2.0-beta, the recommended filename pattern is model-name.query.ts. The previous pattern model-name.prisma-query-options.ts is still supported for backward compatibility, but you cannot use both naming conventions for the same model in the same directory.
Arkos supports customizing options for all standard Prisma operations:
// src/modules/author/author.query.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
import { prisma } from "../../utils/prisma";
const authorPrismaQueryOptions: PrismaQueryOptions<typeof prisma.author> = {
// Global options applied to all operations (params are the same as findMany)
global: { ... },
// Grouped operation options (added in v1.2.0)
find: { ... }, // Controls both findOne and findMany
save: { ... }, // Controls createOne, updateOne, createMany, updateMany
create: { ... }, // Controls both createOne and createMany
update: { ... }, // Controls both updateOne and updateMany
delete: { ... }, // Controls both deleteOne and deleteMany
saveOne: { ... }, // Controls createOne and updateOne
saveMany: { ... }, // Controls createMany and updateMany
// Individual operation options
findOne: { ... },
findMany: { ... },
createOne: { ... },
updateOne: { ... },
deleteOne: { ... },
createMany: { ... },
updateMany: { ... },
deleteMany: { ... }
};
export default authorPrismaQueryOptions;
Follow the file name conventions and folder structure stated above for this to work. Read more about Arkos overall project structure here.
Generating Query Options Files
Since v1.2.0-beta, you can quickly generate query options files using the built-in CLI:
npx arkos generate query-options --module post
# or shorthand
npx arkos g q -m post
This will create a properly structured file at src/modules/post/post.query.{ts|js} with all available options.
Available Query Options
The PrismaQueryOptions type supports all standard Prisma query parameters:
type PrismaQueryOptions<T extends Record<string, any>> = {
// Global options applied to all operations
global?: Partial<Parameters<T["findMany"]>[0]>;
// Grouped options (added in v1.2.0)
find?: Partial<Parameters<T["findMany"]>[0]>; // findMany + findOne
save?: Partial<Parameters<T["create"]>[0]>; // create + update operations
create?: Partial<Parameters<T["create"]>[0]>; // createOne + createMany
update?: Partial<Parameters<T["update"]>[0]>; // updateOne + updateMany
delete?: Partial<Parameters<T["delete"]>[0]>; // deleteOne + deleteMany
saveOne?: Partial<Parameters<T["create"]>[0]>; // createOne + updateOne
saveMany?: Partial<Parameters<T["createMany"]>[0]>; // createMany + updateMany
// Individual operation options
findOne?: Partial<Parameters<T["findFirst"]>[0]>;
findMany?: Partial<Parameters<T["findMany"]>[0]>;
createOne?: Partial<Parameters<T["create"]>[0]>;
updateOne?: Partial<Parameters<T["update"]>[0]>;
deleteOne?: Partial<Parameters<T["delete"]>[0]>;
deleteMany?: Partial<Parameters<T["deleteMany"]>[0]>;
updateMany?: Partial<Parameters<T["updateMany"]>[0]>;
createMany?: Partial<Parameters<T["createMany"]>[0]>;
};
Grouped Options (v1.2.0+)
The grouped options simplify configuration by allowing you to apply settings to multiple related operations at once:
find- Controls bothfindManyandfindOneoperationssave- ControlscreateOne,updateOne,createMany, andupdateManyoperationscreate- Controls bothcreateOneandcreateManyoperationsupdate- Controls bothupdateOneandupdateManyoperationsdelete- Controls bothdeleteOneanddeleteManyoperationssaveOne- Specifically forcreateOneandupdateOneoperationssaveMany- Specifically forcreateManyandupdateManyoperations
These wrapper options reduce repetition when you want the same settings across related operations.
Each operation type supports the full range of Prisma options including:
select: Specify which fields to include in the responseinclude: Configure which relations to loadomit: Specify which fields to omit in the responsewhere: Add default filtersorderBy: Set default sortingtake&skip: Configure pagination defaultsdistinct: Select distinct records
Priority and Merging Behavior
Arkos follows a clear precedence order when applying query options:
- Request query parameters (highest priority)
- Individual operation options (e.g.,
findMany,createOne) - Grouped operation options (e.g.,
find,save) - added in v1.2.0 - 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 here
Usage Examples
Basic Example: Excluding Sensitive Data
// src/modules/user/user.query.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
import { prisma } from "../../utils/prisma";
const userPrismaQueryOptions: PrismaQueryOptions<typeof prisma.user> = {
global: {
select: {
id: true,
name: true,
email: true,
createdAt: true,
updatedAt: true,
// Explicitly exclude sensitive fields
password: false,
resetToken: false,
twoFactorSecret: false,
},
},
};
export default userPrismaQueryOptions;
Using Grouped Options (v1.2.0+)
// src/modules/post/post.query.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
import { prisma } from "../../utils/prisma";
const postPrismaQueryOptions: PrismaQueryOptions<typeof prisma.post> = {
// Apply to all read operations (findOne and findMany)
find: {
where: {
published: true,
},
orderBy: {
createdAt: "desc",
},
},
// Apply to all save operations (create and update)
save: {
include: {
author: true,
tags: true,
},
},
// Override for specific operation if needed
findMany: {
take: 10, // Default page size for lists only
},
};
export default postPrismaQueryOptions;
Advanced Example: Different Behaviors Per Operation
// src/modules/post/post.query.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
import { prisma } from "../../utils/prisma";
const postPrismaQueryOptions: PrismaQueryOptions<typeof prisma.post> = {
// Global defaults for all operations
global: {
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.query.ts
import { Prisma } from "@prisma/client";
import { PrismaQueryOptions } from "arkos/prisma";
import { prisma } from "../../utils/prisma";
const userPrismaQueryOptions: PrismaQueryOptions<typeof prisma.user> = {
global: {
// 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
globalconfiguration - Use Grouped Options: Leverage the grouped options (v1.2.0+) like
find,save,createto reduce repetition - Pagination Defaults: Set reasonable
takelimits to prevent performance issues - Deep Relations: Be cautious with deeply nested
includestatements - Documentation: Document your default behavior for API consumers
- Performance: Use
selectinstead ofincludewhen possible to minimize data transfer - Generate with CLI: Use
npx arkos g q -m modelNameto quickly scaffold query options files
Version History
- v1.2.0-beta:
- Added grouped query options (
find,save,create,update,delete,saveOne,saveMany) - Simplified naming convention from
model.prisma-query-options.tstomodel.query.ts - Added CLI command for generating query options files
- Changed
queryOptionsproperty toglobalfor clarity
- Added grouped query options (