PJPalJS

Prisma GraphQL Modules Generator

Generate modular GraphQL applications using the GraphQL Modules framework with Prisma models. This generator creates scalable, modular GraphQL schemas with proper dependency injection and module separation.

View Template on GitHub

Overview

The Prisma GraphQL Modules Generator leverages the powerful GraphQL Modules framework to create:

  • Modular GraphQL schemas with clear separation of concerns
  • Dependency injection for proper service management
  • Type-safe resolvers with automatic Prisma integration
  • Scalable architecture for enterprise applications
  • Plugin system for extending functionality

Configuration Options

typescript
interface GraphQLModulesGeneratorOptions {
  prismaName?: string; // Prisma client instance name (default: "prisma")
  output?: string; // Output directory (default: "./src/app")
  excludeFields?: string[]; // Fields to exclude globally
  excludeModels?: Array<{
    name: string;
    queries?: boolean;
    mutations?: boolean;
  }>;
  excludeFieldsByModel?: Record<string, string[]>;
  excludeQueriesAndMutations?: string[];
  excludeQueriesAndMutationsByModel?: Record<string, string[]>;
  disableQueries?: boolean; // Disable all query generation
  disableMutations?: boolean; // Disable all mutation generation
}

Generated File Structure

code
src/app/
├── application.ts       # Main application module
├── providers/
│   └── prisma.provider.ts  # Prisma dependency injection
├── inputs/
│   └── InputTypes.ts   # Input types and enums
└── {ModelName}/
    ├── {ModelName}.module.ts  # Module definition
    ├── typeDefs.ts           # Type definitions
    └── resolvers.ts          # Resolvers

Module Architecture

1. Module Definition

Each model gets its own GraphQL module:

typescript
// User/User.module.ts
import { createModule } from "graphql-modules";
import { PrismaProvider } from "../providers/prisma.provider";
import typeDefs from "./typeDefs";
import resolvers from "./resolvers";
 
export const UserModule = createModule({
  id: "User",
  typeDefs,
  resolvers,
  providers: [PrismaProvider],
});

2. Type Definitions with Extensions

typescript
// User/typeDefs.ts
import { gql } from "graphql-modules";
 
export default gql`
  """
  User model representing blog authors and commenters
  """
  type User {
    """
    Unique identifier for the user
    """
    id: String!
 
    """
    User's email address (unique)
    """
    email: String!
 
    """
    User's display name
    """
    name: String
 
    """
    Profile image URL
    """
    avatar: String
 
    """
    User's role in the system
    """
    role: Role!
 
    """
    Posts authored by this user
    """
    posts(
      where: PostWhereInput
      orderBy: [PostOrderByWithRelationInput!]
      cursor: PostWhereUniqueInput
      take: Int
      skip: Int
      distinct: [PostScalarFieldEnum!]
    ): [Post!]!
 
    """
    Comments made by this user
    """
    comments(
      where: CommentWhereInput
      orderBy: [CommentOrderByWithRelationInput!]
      cursor: CommentWhereUniqueInput
      take: Int
      skip: Int
      distinct: [CommentScalarFieldEnum!]
    ): [Comment!]!
  }
 
  extend type Query {
    findUniqueUser(where: UserWhereUniqueInput!): User
    findManyUser(
      where: UserWhereInput
      orderBy: [UserOrderByWithRelationInput!]
      cursor: UserWhereUniqueInput
      take: Int
      skip: Int
      distinct: [UserScalarFieldEnum!]
    ): [User!]!
    findManyUserCount(
      where: UserWhereInput
      orderBy: [UserOrderByWithRelationInput!]
      cursor: UserWhereUniqueInput
      take: Int
      skip: Int
      distinct: [UserScalarFieldEnum!]
    ): Int!
    findFirstUser(
      where: UserWhereInput
      orderBy: [UserOrderByWithRelationInput!]
      cursor: UserWhereUniqueInput
      take: Int
      skip: Int
      distinct: [UserScalarFieldEnum!]
    ): User
    aggregateUser(
      where: UserWhereInput
      orderBy: [UserOrderByWithRelationInput!]
      cursor: UserWhereUniqueInput
      take: Int
      skip: Int
    ): AggregateUser
  }
 
  extend type Mutation {
    createOneUser(data: UserCreateInput!): User!
    updateOneUser(where: UserWhereUniqueInput!, data: UserUpdateInput!): User!
    deleteOneUser(where: UserWhereUniqueInput!): User
    upsertOneUser(
      where: UserWhereUniqueInput!
      create: UserCreateInput!
      update: UserUpdateInput!
    ): User!
    deleteManyUser(where: UserWhereInput): BatchPayload!
    updateManyUser(
      where: UserWhereInput
      data: UserUpdateManyMutationInput!
    ): BatchPayload!
  }
`;

3. Dependency Injection Resolvers

typescript
// User/resolvers.ts
import { PrismaProvider } from "../providers/prisma.provider";
 
export default {
  Query: {
    findUniqueUser: (_parent: any, { where }: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.findUnique({ where });
    },
 
    findManyUser: (_parent: any, args: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.findMany(args);
    },
 
    findManyUserCount: (_parent: any, args: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.count(args);
    },
 
    findFirstUser: (_parent: any, args: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.findFirst(args);
    },
 
    aggregateUser: (_parent: any, args: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.aggregate(args);
    },
  },
 
  Mutation: {
    createOneUser: (_parent: any, { data }: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.create({ data });
    },
 
    updateOneUser: (_parent: any, { where, data }: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.update({ where, data });
    },
 
    deleteOneUser: (_parent: any, { where }: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.delete({ where });
    },
 
    upsertOneUser: (
      _parent: any,
      { where, create, update }: any,
      { injector }: any
    ) => {
      return injector
        .get(PrismaProvider)
        .user.upsert({ where, create, update });
    },
 
    deleteManyUser: (_parent: any, { where }: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.deleteMany({ where });
    },
 
    updateManyUser: (_parent: any, { where, data }: any, { injector }: any) => {
      return injector.get(PrismaProvider).user.updateMany({ where, data });
    },
  },
 
  User: {
    posts: (parent: any, args: any, { injector }: any) => {
      return injector
        .get(PrismaProvider)
        .user.findUnique({
          where: { id: parent.id },
        })
        .posts(args);
    },
 
    comments: (parent: any, args: any, { injector }: any) => {
      return injector
        .get(PrismaProvider)
        .user.findUnique({
          where: { id: parent.id },
        })
        .comments(args);
    },
  },
};

Application Composition

Main Application Module

typescript
// application.ts
import { createApplication } from "graphql-modules";
import { UserModule } from "./User/User.module";
import { PostModule } from "./Post/Post.module";
import { CommentModule } from "./Comment/Comment.module";
 
export const application = createApplication({
  modules: [UserModule, PostModule, CommentModule],
});
 
export const schema = application.createSchemaForApollo();

Prisma Provider

typescript
// providers/prisma.provider.ts
import { Injectable, Scope } from "graphql-modules";
import { PrismaClient } from "@prisma/client";
 
@Injectable({
  scope: Scope.Singleton,
})
export class PrismaProvider extends PrismaClient {
  constructor() {
    super();
  }
 
  async onModuleInit() {
    await this.$connect();
  }
 
  async onModuleDestroy() {
    await this.$disconnect();
  }
}

Real-World Example

Sample Prisma Schema

prisma
// schema.prisma
model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  avatar    String?
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  posts    Post[]
  comments Comment[]
}
 
model Post {
  id        String   @id @default(uuid())
  title     String
  content   String?
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  authorId String
  author   User      @relation(fields: [authorId], references: [id])
  comments Comment[]
  tags     Tag[]
}
 
model Comment {
  id        String   @id @default(uuid())
  content   String
  approved  Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  postId   String
  post     Post   @relation(fields: [postId], references: [id])
  authorId String
  author   User   @relation(fields: [authorId], references: [id])
}
 
model Tag {
  id    String @id @default(uuid())
  name  String @unique
  posts Post[]
}
 
enum Role {
  USER
  ADMIN
  MODERATOR
}

AI Prompt

code
I need to generate a modular GraphQL application using GraphQL Modules framework with my Prisma schema.

Please follow the Prisma GraphQL Modules Generator template with this configuration:
- Output directory: src/app
- Exclude fields: ["createdAt", "updatedAt"]
- Models to generate: ["User", "Post", "Comment", "Tag"]
- Generate modules with dependency injection
- Prisma client name: "prisma"

[Include the complete Prisma GraphQL Modules Generator template from GitHub]

Here's my Prisma schema:
[Include the schema above]

Generate the modular GraphQL application following the template patterns with proper:
- Module definitions
- Type definitions with extensions
- Dependency injection resolvers
- Application composition
- Provider setup

Generated Server Setup

src/server.ts:

typescript
import { ApolloServer } from "apollo-server-express";
import express from "express";
import { application, schema } from "./app/application";
 
async function startServer() {
  const app = express();
 
  const server = new ApolloServer({
    schema,
    executor: application.createApolloExecutor(),
    context: (session) => session, // GraphQL Modules handles context
    introspection: true,
    playground: true,
  });
 
  await server.start();
  server.applyMiddleware({ app, path: "/graphql" });
 
  const port = process.env.PORT || 4000;
 
  app.listen(port, () => {
    console.log(
      `🚀 Server ready at http://localhost:${port}${server.graphqlPath}`
    );
  });
}
 
startServer().catch((error) => {
  console.error("Error starting server:", error);
});

Module Extensions for Relations

Post/typeDefs.ts with User relations:

typescript
import { gql } from "graphql-modules";
 
export default gql`
  type Post {
    id: String!
    title: String!
    content: String
    published: Boolean!
 
    author: User!
    comments(
      where: CommentWhereInput
      orderBy: [CommentOrderByWithRelationInput!]
      cursor: CommentWhereUniqueInput
      take: Int
      skip: Int
    ): [Comment!]!
    tags(
      where: TagWhereInput
      orderBy: [TagOrderByWithRelationInput!]
      cursor: TagWhereUniqueInput
      take: Int
      skip: Int
    ): [Tag!]!
  }
 
  # Extend User type with posts relation
  extend type User {
    posts(
      where: PostWhereInput
      orderBy: [PostOrderByWithRelationInput!]
      cursor: PostWhereUniqueInput
      take: Int
      skip: Int
    ): [Post!]!
  }
 
  # Extend Tag type with posts relation
  extend type Tag {
    posts(
      where: PostWhereInput
      orderBy: [PostOrderByWithRelationInput!]
      cursor: PostWhereUniqueInput
      take: Int
      skip: Int
    ): [Post!]!
  }
 
  extend type Query {
    findUniquePost(where: PostWhereUniqueInput!): Post
    findManyPost(
      where: PostWhereInput
      orderBy: [PostOrderByWithRelationInput!]
      cursor: PostWhereUniqueInput
      take: Int
      skip: Int
    ): [Post!]!
    findManyPostCount(
      where: PostWhereInput
      orderBy: [PostOrderByWithRelationInput!]
      cursor: PostWhereUniqueInput
      take: Int
      skip: Int
    ): Int!
  }
 
  extend type Mutation {
    createOnePost(data: PostCreateInput!): Post!
    updateOnePost(where: PostWhereUniqueInput!, data: PostUpdateInput!): Post!
    deleteOnePost(where: PostWhereUniqueInput!): Post
  }
`;

Advanced Features

Custom Providers and Services

typescript
// providers/auth.provider.ts
import { Injectable } from "graphql-modules";
import jwt from "jsonwebtoken";
 
@Injectable()
export class AuthProvider {
  verifyToken(token: string) {
    return jwt.verify(token, process.env.JWT_SECRET!);
  }
 
  generateToken(payload: any) {
    return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: "7d" });
  }
}
 
// Use in module
export const UserModule = createModule({
  id: "User",
  typeDefs,
  resolvers,
  providers: [PrismaProvider, AuthProvider],
});

Enhanced Resolvers with Services

typescript
// User/resolvers.ts
import { PrismaProvider } from "../providers/prisma.provider";
import { AuthProvider } from "../providers/auth.provider";
 
export default {
  Query: {
    me: async (_parent: any, _args: any, { injector, request }: any) => {
      const authProvider = injector.get(AuthProvider);
      const prismaProvider = injector.get(PrismaProvider);
 
      const token = request.headers.authorization?.replace("Bearer ", "");
      if (!token) throw new Error("Authentication required");
 
      const payload = authProvider.verifyToken(token);
      return prismaProvider.user.findUnique({ where: { id: payload.userId } });
    },
  },
 
  Mutation: {
    login: async (
      _parent: any,
      { email, password }: any,
      { injector }: any
    ) => {
      const authProvider = injector.get(AuthProvider);
      const prismaProvider = injector.get(PrismaProvider);
 
      const user = await prismaProvider.user.findUnique({ where: { email } });
      if (!user) throw new Error("Invalid credentials");
 
      // Add password verification logic here
 
      const token = authProvider.generateToken({ userId: user.id });
      return { user, token };
    },
  },
};

Middleware Integration

typescript
// middleware/auth.middleware.ts
import { MiddlewareFn } from "graphql-modules";
 
export const AuthMiddleware: MiddlewareFn = ({ context }, next) => {
  // Add authentication logic
  return next();
};
 
// Apply to module
export const UserModule = createModule({
  id: "User",
  typeDefs,
  resolvers,
  providers: [PrismaProvider],
  middlewares: [AuthMiddleware],
});

Module Configuration

typescript
// config/module.config.ts
export const ModuleConfig = {
  pagination: {
    defaultTake: 20,
    maxTake: 100,
  },
  authentication: {
    required: ["createOneUser", "updateOneUser"],
  },
};
 
// Use in resolvers
const resolvers = {
  Query: {
    findManyUser: (_parent: any, args: any, { injector }: any) => {
      const take = Math.min(
        args.take || ModuleConfig.pagination.defaultTake,
        ModuleConfig.pagination.maxTake
      );
      return injector.get(PrismaProvider).user.findMany({ ...args, take });
    },
  },
};

Testing with GraphQL Modules

Unit Testing Modules

typescript
// User/User.module.test.ts
import { testKit } from "graphql-modules";
import { UserModule } from "./User.module";
 
describe("UserModule", () => {
  test("should find unique user", async () => {
    const { execute } = testKit([UserModule]);
 
    const result = await execute(
      `
      query FindUser($where: UserWhereUniqueInput!) {
        findUniqueUser(where: $where) {
          id
          email
          name
        }
      }
    `,
      {
        variableValues: {
          where: { id: "user-1" },
        },
      }
    );
 
    expect(result.errors).toBeUndefined();
    expect(result.data?.findUniqueUser).toBeDefined();
  });
});

Integration Testing

typescript
// tests/integration.test.ts
import { testKit } from "graphql-modules";
import { application } from "../src/app/application";
 
describe("Application Integration", () => {
  test("should create user with post", async () => {
    const { execute } = testKit([application]);
 
    const createUserResult = await execute(
      `
      mutation CreateUser($data: UserCreateInput!) {
        createOneUser(data: $data) {
          id
          email
          name
        }
      }
    `,
      {
        variableValues: {
          data: {
            email: "test@example.com",
            name: "Test User",
          },
        },
      }
    );
 
    expect(createUserResult.errors).toBeUndefined();
    const userId = createUserResult.data?.createOneUser.id;
 
    const createPostResult = await execute(
      `
      mutation CreatePost($data: PostCreateInput!) {
        createOnePost(data: $data) {
          id
          title
          author {
            id
            name
          }
        }
      }
    `,
      {
        variableValues: {
          data: {
            title: "Test Post",
            content: "This is a test post",
            author: { connect: { id: userId } },
          },
        },
      }
    );
 
    expect(createPostResult.errors).toBeUndefined();
    expect(createPostResult.data?.createOnePost.author.id).toBe(userId);
  });
});

Performance Optimizations

DataLoader Integration

typescript
// providers/dataloader.provider.ts
import { Injectable } from "graphql-modules";
import DataLoader from "dataloader";
import { PrismaProvider } from "./prisma.provider";
 
@Injectable()
export class DataLoaderProvider {
  constructor(private prisma: PrismaProvider) {}
 
  userLoader = new DataLoader(async (userIds: string[]) => {
    const users = await this.prisma.user.findMany({
      where: { id: { in: userIds } },
    });
    return userIds.map((id) => users.find((user) => user.id === id));
  });
 
  postsByAuthorLoader = new DataLoader(async (authorIds: string[]) => {
    const posts = await this.prisma.post.findMany({
      where: { authorId: { in: authorIds } },
    });
    return authorIds.map((authorId) =>
      posts.filter((post) => post.authorId === authorId)
    );
  });
}

Caching with GraphQL Modules

typescript
// providers/cache.provider.ts
import { Injectable } from "graphql-modules";
import { LRUCache } from "lru-cache";
 
@Injectable()
export class CacheProvider {
  private cache = new LRUCache<string, any>({
    max: 1000,
    ttl: 1000 * 60 * 5, // 5 minutes
  });
 
  get<T>(key: string): T | undefined {
    return this.cache.get(key);
  }
 
  set(key: string, value: any): void {
    this.cache.set(key, value);
  }
 
  del(key: string): void {
    this.cache.delete(key);
  }
}

Deployment Considerations

Schema Composition

typescript
// scripts/build-schema.ts
import { printSchema } from "graphql";
import { writeFileSync } from "fs";
import { application } from "../src/app/application";
 
const schema = application.createSchemaForApollo();
const schemaString = printSchema(schema);
 
writeFileSync("./schema.graphql", schemaString);
console.log("Schema written to schema.graphql");

Environment Configuration

typescript
// config/environment.ts
export const config = {
  database: {
    url: process.env.DATABASE_URL!,
  },
  server: {
    port: Number(process.env.PORT) || 4000,
    cors: {
      origin: process.env.CORS_ORIGIN || "*",
    },
  },
  auth: {
    jwtSecret: process.env.JWT_SECRET!,
    tokenExpiry: process.env.TOKEN_EXPIRY || "7d",
  },
};

Best Practices

Module Organization

  • One module per domain/model - Keep related functionality together
  • Provider separation - Use providers for services, not data access
  • Type safety - Leverage TypeScript for all modules
  • Testing - Write unit tests for each module
  • Documentation - Document module purposes and dependencies

Performance

  • Use DataLoaders - Prevent N+1 query problems
  • Implement caching - Cache frequently accessed data
  • Optimize queries - Use proper field selection
  • Monitor performance - Track query execution times

Security

  • Authentication middleware - Protect sensitive operations
  • Input validation - Validate all user inputs
  • Rate limiting - Prevent abuse
  • Error handling - Don't expose sensitive information

Benefits of GraphQL Modules

Modularity

  • Independent development - Teams can work on different modules
  • Code reusability - Modules can be shared across projects
  • Clear boundaries - Each module has defined responsibilities

Scalability

  • Lazy loading - Modules load only when needed
  • Dependency injection - Proper service management
  • Testing isolation - Test modules independently

Maintainability

  • Separation of concerns - Clear module boundaries
  • Type safety - Comprehensive TypeScript support
  • Documentation - Self-documenting module structure

Next Steps

Command Palette

Search for a command to run...