Eliminate GraphQL over-fetching and under-fetching with automatic Prisma select/include generation
GQLPrismaSelect automatically converts your GraphQL query selections into optimized Prisma select and include objects. This ensures your database queries only fetch the data you actually need, improving performance and reducing bandwidth usage.
- π Performance: Fetch only the data your GraphQL queries request, including nested data
- π― Precision: Eliminate over-fetching and under-fetching automatically
- ποΈ Arguments Support: Pass Prisma arguments (take, skip, orderBy) directly in GraphQL queries
- π§ Simple: Drop-in replacement for manual select/include objects
- π οΈ Flexible: Support for nested relations, advanced fragment handling, and custom transformations
- π¨ Modern: Built for Apollo Server and Prisma ecosystems
- π Quick Start
- β¨ Features
- π¦ Installation
- π§ Basic Usage
- π Advanced Features
- π Examples
- π API Reference
- π Migration Guide
- π€ Contributing
Get started in 3 simple steps:
npm install @nazariistrohush/gql-prisma-selectimport { GQLPrismaSelect } from '@nazariistrohush/gql-prisma-select';
@Query(() => User)
async user(@Info() info: GraphQLResolveInfo, @Args('id') id: number) {
const { include, select } = new GQLPrismaSelect(info);
return this.prisma.user.findUnique({
where: { id },
include,
select,
});
}query {
user(id: 1) {
id
email
posts {
id
title
}
}
}That's it! Your Prisma query now only fetches id, email from users and id, title from posts - no more, no less.
GQLPrismaSelect provides a comprehensive set of features to optimize your GraphQL-Prisma integration:
- Automatic Selection Generation: Convert GraphQL queries to Prisma select/include objects
- Prisma Arguments Support: Use
take,skip,orderByand more directly in nested relations - Nested Relations: Handle complex nested queries with multiple levels of relations
- Fragment Support: Full support for GraphQL fragments and inline fragments
- Field Exclusion: Automatically exclude unwanted fields like
__typename
- Field Transformations: Transform field names (camelCase β snake_case, pluralization)
- Custom Field Mapping: Map GraphQL fields to different Prisma field names
- Path-based Selection: Extract specific parts of selections by path
- Result Transformation: Transform query results with custom functions
- Type-Safe Integration: Advanced TypeScript utilities with compile-time type checking and IntelliSense
- Framework Agnostic: Works with any GraphQL framework (NestJS, Apollo Server, etc.)
- Performance Optimized: Minimal overhead with efficient parsing
- Comprehensive Testing: High test coverage ensures reliability
npm install @nazariistrohush/gql-prisma-select- Node.js: >= 14.16.0
- Prisma: Any recent version
- GraphQL Server: Apollo Server or compatible
npm install prisma @apollo/server graphqlThe GQLPrismaSelect constructor takes a GraphQLResolveInfo object and optional configuration:
new GQLPrismaSelect(info, options?)Parameters:
info:GraphQLResolveInfo- The GraphQL resolve info object from your resolveroptions:Object(optional) - Configuration options
Returns:
include: Prisma include object for relationsselect: Prisma select object for scalar fieldsoriginalInclude: Untransformed include objectoriginalSelect: Untransformed select object
import { GQLPrismaSelect } from '@nazariistrohush/gql-prisma-select';
@Resolver(() => User)
export class UserResolver {
constructor(private prisma: PrismaClient) {}
@Query(() => User)
async user(@Info() info: GraphQLResolveInfo, @Args('id') id: number) {
const { include, select } = new GQLPrismaSelect(info);
return this.prisma.user.findUnique({
where: { id },
include,
select,
});
}
}GQLPrismaSelect automatically handles nested relations:
@Query(() => User)
async userWithPosts(@Info() info: GraphQLResolveInfo, @Args('id') id: number) {
const { include, select } = new GQLPrismaSelect(info);
// This will include posts and their authors if requested in the query
return this.prisma.user.findUnique({
where: { id },
include,
select,
});
}One of the best features of gql-prisma-select is the ability to pass Prisma arguments like take, skip, orderBy, where, and distinct directly from your GraphQL query. This works for both nested relations and root-level fields.
GraphQL Query:
query {
users(take: 10, orderBy: { createdAt: desc }) {
id
email
posts(take: 5, skip: 1, orderBy: { title: asc }) {
title
}
}
}Resulting Prisma Query (Automatically Generated):
// Nested arguments are automatically injected into select/include
{
select: {
id: true,
email: true,
posts: {
select: { title: true },
take: 5,
skip: 1,
orderBy: { title: 'asc' }
}
}
}
// Root arguments are available via .args property
const gqlSelect = new GQLPrismaSelect(info);
// gqlSelect.args = { take: 10, orderBy: { createdAt: 'desc' } }By default, __typename fields are excluded. Add custom exclusions:
const { include, select } = new GQLPrismaSelect(info, {
excludeFields: ['__typename', 'internalField']
});Extract specific parts of selections using paths:
const selector = new GQLPrismaSelect(info);
// Get selection for User relation
const { include: userInclude, select: userSelect } = GQLPrismaSelect.get(
selector,
'posts.author' // lodash-style path
);
// Get selection for Post relation
const { include: postInclude, select: postSelect } = GQLPrismaSelect.get(
selector,
['posts'] // array path
);Transform field names between GraphQL and Prisma schemas:
const { include, select } = new GQLPrismaSelect(info, {
transforms: {
fieldTransforms: {
// Map GraphQL field to different Prisma field
'fullName': 'full_name',
'createdAt': 'created_at'
},
defaultTransforms: ['camelToSnake'], // Auto-convert camelCase to snake_case
transformRelations: true // Also transform relation fields
}
});Built-in transformers:
camelToSnake:userNameβuser_namesnakeToCamel:user_nameβuserNamepluralize:userβuserssingularize:usersβuser
Map GraphQL fields to completely different Prisma fields:
const { include, select } = new GQLPrismaSelect(info, {
transforms: {
fieldTransforms: {
'displayName': (value, context) => {
// Custom logic for field transformation
if (context.modelName === 'User') {
return 'full_name';
}
return value;
}
}
}
});GQLPrismaSelect fully supports GraphQL fragments:
fragment UserFields on User {
id
email
profile {
firstName
lastName
}
}
query {
users {
...UserFields
posts {
id
title
}
}
}The library automatically resolves fragment selections into the appropriate Prisma select/include objects.
GQLPrismaSelect now supports advanced fragment handling capabilities:
Fragment Registry & Caching
import { FragmentRegistry, FragmentCache } from '@nazariistrohush/gql-prisma-select';
// Register fragments for reuse
FragmentRegistry.register({
name: 'UserBasic',
type: 'User',
selections: { id: true, email: true, name: true },
metadata: { size: 30, complexity: 3, dependencies: [], usageCount: 0, lastUsed: new Date() }
});
// Use cached fragments
const cache = new FragmentCache({ enabled: true, ttl: 300000 });Fragment Overrides
import { FragmentOverrider } from '@nazariistrohush/gql-prisma-select';
const override = {
fragmentName: 'UserBasic',
excludeFields: ['internalId'],
includeFields: ['avatar'],
transformFields: { 'fullName': 'display_name' },
removeSelections: ['profile.private']
};
const customizedFragment = FragmentOverrider.apply(baseFragment, override);Dynamic Fragments
import { DynamicFragmentHandler } from '@nazariistrohush/gql-prisma-select';
const dynamicFragments = [
{
name: 'AdminOnly',
condition: (ctx) => ctx.user?.role === 'admin',
selections: { adminData: true, permissions: true },
priority: 1
},
{
name: 'PremiumUser',
condition: (ctx) => ctx.user?.subscription === 'premium',
selections: { premiumFeatures: true, analytics: true },
priority: 2
}
];
const context = { user: { role: 'admin', subscription: 'premium' } };
const activeFragments = DynamicFragmentHandler.evaluate(dynamicFragments, context);Fragment Optimization
import { FragmentOptimizer, FragmentAnalyzer } from '@nazariistrohush/gql-prisma-select';
// Optimize fragments for performance
const optimized = FragmentOptimizer.inline(fragment, 5); // Inline if used < 5 times
// Analyze fragment usage
const analysis = FragmentAnalyzer.analyze(allFragments);
console.log('Optimization suggestions:', analysis.opportunities);Transform query results after fetching:
const selector = new GQLPrismaSelect(info, {
transforms: {
resultTransforms: {
'User.email': (value) => value.toLowerCase(),
'Post.title': (value) => value.trim()
}
}
});
// Access transformed results
const result = await prisma.user.findUnique({...});
const transformedResult = selector.transformResult(result);Future versions will include Prisma schema validation:
const { include, select } = new GQLPrismaSelect(info, {
schema: prismaSchema, // Validate against Prisma schema
validateFields: true,
optimizeQueries: true
});GQLPrismaSelect provides advanced TypeScript utilities for compile-time type safety and enhanced developer experience.
Get full type safety with IntelliSense support for GraphQL and Prisma integration:
import { TypedGQLPrismaSelect } from '@nazariistrohush/gql-prisma-select';
interface GraphQLUser {
id: string;
name: string;
email: string;
posts: {
id: string;
title: string;
content: string;
};
}
export const userResolver = async (
parent: any,
args: { id: string },
context: any,
info: GraphQLResolveInfo
) => {
const selector = new TypedGQLPrismaSelect<GraphQLUser, 'User'>(info);
// Fully typed with IntelliSense
const select = selector.getTypedSelect();
const include = selector.getTypedInclude();
return context.prisma.user.findUnique({
where: { id: args.id },
select,
include
});
};Validate selections against your GraphQL schema at runtime:
import { TypedGQLPrismaSelect } from '@nazariistrohush/gql-prisma-select';
const selector = new TypedGQLPrismaSelect<User, 'User'>(info, {
typeValidation: {
strict: true,
validateEnums: true,
validateRelations: true
}
});
// Validate selections against schema
const validationResult = selector.validateTypes(schema);
if (!validationResult.isValid) {
console.error('Type validation errors:', validationResult.errors);
}Build type-safe Prisma queries with a fluent API:
import { TypedQueryBuilder } from '@nazariistrohush/gql-prisma-select';
const builder = new TypedQueryBuilder({ model: 'User' });
const query = builder
.select({ id: true, name: true, email: true })
.include({
posts: {
select: { id: true, title: true },
include: { comments: true }
}
})
.where({ active: true })
.orderBy({ createdAt: 'desc' })
.build();
// query is fully typed and ready for Prisma
const users = await prisma.user.findMany(query);Generate TypeScript types from your GraphQL schema:
import { TypeGenerator } from '@nazariistrohush/gql-prisma-select';
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
`);
const generator = new TypeGenerator(schema, {
output: './generated',
generateQueries: true,
generateMutations: true
});
// Generate all types
await generator.generate();Built-in integrations for popular GraphQL libraries:
// Nexus Integration
import { NexusIntegration } from '@nazariistrohush/gql-prisma-select';
const UserQuery = NexusIntegration.createQueryField<'User'>({
type: 'User',
args: { id: idArg() },
model: 'User',
resolve: async (root, args, ctx, info) => {
const selector = new TypedGQLPrismaSelect<any, 'User'>(info);
const { select, include } = selector.getTypedSelect();
return ctx.prisma.user.findUnique({
where: { id: args.id },
select,
include
});
}
});
// Apollo Server Integration
import { ApolloServerIntegration } from '@nazariistrohush/gql-prisma-select';
const resolvers = ApolloServerIntegration.createResolvers({
Query: {
user: ApolloServerIntegration.createQueryResolver<'User'>(
'User',
async (args, context, info) => {
const selector = new TypedGQLPrismaSelect<any, 'User'>(info);
const select = selector.getTypedSelect();
return context.prisma.user.findUnique({
where: { id: args.id },
...select
});
}
)
}
});@Resolver(() => User)
export class UserResolver {
@Query(() => User)
async user(@Info() info: GraphQLResolveInfo) {
const { include, select } = new GQLPrismaSelect(info);
return this.prisma.user.findUnique({ include, select });
}
}const resolvers = {
Query: {
user: async (parent, args, context, info) => {
const { include, select } = new GQLPrismaSelect(info);
return prisma.user.findUnique({ where: { id: args.id }, include, select });
}
}
};const root = {
user: async (args, context, info) => {
const { include, select } = new GQLPrismaSelect(info);
return prisma.user.findUnique({ where: { id: args.id }, include, select });
}
};Prisma Schema:
model User {
id Int @id @default(autoincrement())
email String @unique
firstName String
lastName String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String
author User @relation(fields: [authorId], references: [id])
authorId Int
}GraphQL Schema:
@ObjectType()
class User {
@Field(() => Int)
id: number;
@Field(() => String)
email: string;
@Field(() => String)
firstName: string;
@Field(() => String)
lastName: string;
@Field(() => [Post])
posts: Post[];
}
@ObjectType()
class Post {
@Field(() => Int)
id: number;
@Field(() => String)
title: string;
@Field(() => String)
content: string;
@Field(() => User)
author: User;
}Resolver:
@Resolver(() => User)
export class UserResolver {
constructor(private prisma: PrismaClient) {}
@Query(() => User)
async user(@Info() info: GraphQLResolveInfo, @Args('id') id: number) {
const { include, select } = new GQLPrismaSelect(info);
return this.prisma.user.findUnique({
where: { id },
include,
select,
});
}
}Query:
query GetUserDetails($userId: Int!) {
user(id: $userId) {
id
email
firstName
lastName
posts {
id
title
content
author {
id
email
}
}
}
}Generated Prisma Query:
{
include: {
posts: {
include: {
author: {
select: {
id: true,
email: true
}
}
},
select: {
id: true,
title: true,
content: true
}
}
},
select: {
id: true,
email: true,
firstName: true,
lastName: true
}
}Use Case: GraphQL uses camelCase, Prisma uses snake_case
const { include, select } = new GQLPrismaSelect(info, {
transforms: {
defaultTransforms: ['camelToSnake'],
transformRelations: true
}
});
// GraphQL: firstName, lastName, createdAt
// Prisma: first_name, last_name, created_atfragment UserBasic on User {
id
email
firstName
lastName
}
fragment PostDetails on Post {
id
title
content
author {
...UserBasic
}
}
query GetPosts {
posts {
...PostDetails
createdAt
}
}The library automatically expands fragments into the full selection set.
const { include, select } = new GQLPrismaSelect(info, {
transforms: {
fieldTransforms: {
'fullName': 'full_name', // Simple mapping
'displayName': (value, context) => { // Custom function
return context.modelName === 'User' ? 'full_name' : value;
}
}
}
});new GQLPrismaSelect(info: GraphQLResolveInfo, options?: GQLPrismaSelectOptions)| Option | Type | Description | Default |
|---|---|---|---|
excludeFields |
string[] |
Fields to exclude from selection | ['__typename'] |
get |
string | string[] |
Path to extract specific selection | undefined |
transforms |
TransformOptions |
Field and result transformation options | undefined |
interface TransformOptions {
fieldTransforms?: FieldTransforms; // Custom field mappings
defaultTransforms?: TransformType[]; // Built-in transformers
transformRelations?: boolean; // Transform relation fields
transformEnums?: boolean; // Transform enum values
caseSensitive?: boolean; // Case sensitivity
customTransformers?: Record<string, Function>; // Custom transformer functions
}| Property | Type | Description |
|---|---|---|
include |
object |
Prisma include object for relations |
select |
object |
Prisma select object for scalar fields |
args |
object |
Extracted root-level arguments (take, skip, etc.) |
originalInclude |
object |
Include object without transformations |
originalSelect |
object |
Select object without transformations |
Extracts specific parts of selections by path.
GQLPrismaSelect.get(selector, 'posts.author'); // lodash-style path
GQLPrismaSelect.get(selector, ['posts', 'author']); // array pathParameters:
selection:GQLPrismaSelectinstance or selection objectpath:string | string[]- Path to extract
Returns: { include, select } object for the specified path
| Transformer | Input | Output | Example |
|---|---|---|---|
camelToSnake |
userName |
user_name |
firstName β first_name |
snakeToCamel |
user_name |
userName |
last_name β lastName |
pluralize |
user |
users |
category β categories |
singularize |
users |
user |
categories β category |
The library throws descriptive errors for:
- Invalid GraphQL resolve info
- Circular fragment references
- Invalid transformation configurations
- Path extraction failures
new TypedGQLPrismaSelect<TGraphQL, TPrisma>(
info: GraphQLResolveInfo,
options?: TypedOptions<TGraphQL, TPrisma>
)Advanced type-safe version of GQLPrismaSelect with full IntelliSense support.
Type Parameters:
TGraphQL: GraphQL type interface (e.g.,{ id: string; name: string })TPrisma: Prisma model name as string literal (e.g.,'User')
Options:
| Option | Type | Description | Default |
|---|---|---|---|
excludeFields |
string[] |
Fields to exclude from selection | ['__typename'] |
transforms |
TransformOptions |
Field transformation options | undefined |
fragments |
FragmentOptions |
Fragment handling options | undefined |
typeValidation |
TypeValidationOptions |
Runtime type validation options | undefined |
Methods:
| Method | Returns | Description |
|---|---|---|
getTypedSelect() |
SafeSelect<TGraphQL, TPrisma> |
Type-safe select object with full IntelliSense |
getTypedInclude() |
PrismaSelect<TPrisma> |
Type-safe include object for relations |
validateTypes(schema?) |
ValidationResult |
Runtime validation against GraphQL schema |
transformResultTyped(result) |
any |
Transform result with type validation |
new TypedQueryBuilder<TModel>(options: TypedQueryBuilderOptions<TModel>)Fluent API for building type-safe Prisma queries.
Type Parameters:
TModel: Prisma model name as string literal (e.g.,'User')
Options:
| Option | Type | Description | Default |
|---|---|---|---|
model |
TModel |
Prisma model name | Required |
schema |
GraphQLSchema |
GraphQL schema for validation | undefined |
typeValidation |
boolean |
Enable type validation | true |
Methods:
| Method | Returns | Description |
|---|---|---|
select(fields) |
this |
Add fields to select (type-safe) |
include(relations) |
this |
Add relations to include (type-safe) |
where(conditions) |
this |
Add where conditions |
orderBy(order) |
this |
Add ordering |
build() |
TypedPrismaQuery<TModel> |
Build complete query object |
getModel() |
TModel |
Get model name |
new TypeGenerator(schema: GraphQLSchema, options?: Partial<TypeGenerationOptions>)Generate TypeScript types from GraphQL schemas.
Options:
| Option | Type | Description | Default |
|---|---|---|---|
output |
string |
Output directory | './generated' |
generateQueries |
boolean |
Generate query types | true |
generateMutations |
boolean |
Generate mutation types | true |
generateSubscriptions |
boolean |
Generate subscription types | true |
customScalars |
Record<string, string> |
Custom scalar mappings | {} |
namespace |
string |
Type namespace | 'Generated' |
Methods:
| Method | Returns | Description |
|---|---|---|
generateTypes() |
TypeGenerationResult |
Generate all types |
generateModelTypes() |
string |
Generate model types |
generateQueryTypes() |
string |
Generate query types |
Static Methods:
| Method | Returns | Description |
|---|---|---|
generate(options) |
Promise<void> |
Generate types to files |
generateForModel(modelName, schema) |
string |
Generate types for specific model |
new TypeValidator(schema: GraphQLSchema, options?: TypeValidationOptions)Runtime type validation against GraphQL schemas.
Options:
| Option | Type | Description | Default |
|---|---|---|---|
strict |
boolean |
Throw on type mismatches | false |
warnOnMissing |
boolean |
Warn on missing fields | true |
validateEnums |
boolean |
Validate enum values | true |
validateRelations |
boolean |
Validate relation types | true |
Methods:
| Method | Returns | Description |
|---|---|---|
validate(value, type) |
ValidationResult |
Validate value against type |
validateSelection(selection, type) |
ValidationResult |
Validate GraphQL selection |
validateEnum(value, enumType) |
boolean |
Validate enum value |
validateRelations(selections, parentType) |
ValidationResult |
Validate relations |
| Method | Description |
|---|---|
createQueryField<TModel>(config) |
Create typed Nexus query field |
createMutationField<TModel>(config) |
Create typed Nexus mutation field |
createResolvers<TModel>(model, resolvers) |
Create typed field resolvers |
| Method | Description |
|---|---|
createResolvers(resolvers) |
Create resolver map |
createQueryResolver<TModel>(model, resolver) |
Create typed query resolver |
createMutationResolver<TModel>(model, resolver) |
Create typed mutation resolver |
| Method | Description |
|---|---|
register(name, integration) |
Register library integration |
get(name) |
Get registered integration |
list() |
List all registered integrations |
Before (Manual):
@Query(() => User)
async user(@Args('id') id: number) {
return this.prisma.user.findUnique({
where: { id },
select: {
id: true,
email: true,
firstName: true,
lastName: true,
posts: {
select: {
id: true,
title: true,
content: true
}
}
}
});
}After (GQLPrismaSelect):
@Query(() => User)
async user(@Info() info: GraphQLResolveInfo, @Args('id') id: number) {
const { include, select } = new GQLPrismaSelect(info);
return this.prisma.user.findUnique({
where: { id },
include,
select,
});
}// graphql-fields approach
import graphqlFields from 'graphql-fields';
const fields = graphqlFields(info);
// Manual processing required...
// GQLPrismaSelect approach
const { include, select } = new GQLPrismaSelect(info);
// Ready for Prisma!- Automatic: No need to manually define select/include objects
- Accurate: Fetches exactly what GraphQL requests
- Maintainable: Changes to GraphQL schema automatically reflected
- Type-Safe: Full TypeScript support
- Flexible: Advanced features like transformations and fragments
Replace static select objects with dynamic generation.
The library handles all nesting automatically.
Use excludeFields option instead of manual filtering.
Use field transformations for complex mappings.
We welcome contributions! Please see our Contributing Guide for details.
git clone https://github.com/NazariiStrohush/gql-prisma-select.git
cd gql-prisma-select
npm install
npm run build
npm test- Use GitHub Issues for bugs
- Check existing issues before creating new ones
- Include code examples and GraphQL schemas when possible
- Open a GitHub Issue with the "enhancement" label
- Describe the use case and expected behavior
- Consider submitting a pull request if you have implementation ideas
ISC License - see the LICENSE file for details.
- Built for the Apollo Server and Prisma ecosystems
- Inspired by the need for better GraphQL-Prisma integration
- Thanks to all contributors and the open-source community