$npx -y skills add LambdaTest/agent-skills --skill graphql-grpc-helperDesigns GraphQL schemas, resolvers, query/mutation/subscription patterns, and protobuf definitions for gRPC services. Use whenever the user asks about GraphQL, "design a GraphQL schema", "write mutations for", "GraphQL subscriptions", "DataLoader pattern", "gRPC service", "protob
| 1 | # GraphQL & gRPC Skill |
| 2 | |
| 3 | Design schemas, resolvers, and service definitions for GraphQL and gRPC APIs. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## GraphQL Schema Design |
| 8 | |
| 9 | ```graphql |
| 10 | # Scalars |
| 11 | scalar DateTime |
| 12 | scalar UUID |
| 13 | scalar JSON |
| 14 | |
| 15 | # Enums |
| 16 | enum OrderStatus { PENDING PAID SHIPPED DELIVERED CANCELLED } |
| 17 | enum UserRole { ADMIN EDITOR VIEWER } |
| 18 | |
| 19 | # Types |
| 20 | type User { |
| 21 | id: UUID! |
| 22 | name: String! |
| 23 | email: String! |
| 24 | role: UserRole! |
| 25 | orders(first: Int, after: String): OrderConnection! |
| 26 | createdAt: DateTime! |
| 27 | } |
| 28 | |
| 29 | type Order { |
| 30 | id: UUID! |
| 31 | status: OrderStatus! |
| 32 | total: Float! |
| 33 | items: [OrderItem!]! |
| 34 | user: User! |
| 35 | createdAt: DateTime! |
| 36 | } |
| 37 | |
| 38 | type OrderItem { |
| 39 | id: UUID! |
| 40 | product: Product! |
| 41 | quantity: Int! |
| 42 | price: Float! |
| 43 | } |
| 44 | |
| 45 | # Pagination (Relay cursor spec) |
| 46 | type OrderConnection { |
| 47 | edges: [OrderEdge!]! |
| 48 | pageInfo: PageInfo! |
| 49 | totalCount: Int! |
| 50 | } |
| 51 | type OrderEdge { node: Order!; cursor: String! } |
| 52 | type PageInfo { |
| 53 | hasNextPage: Boolean! |
| 54 | hasPreviousPage: Boolean! |
| 55 | startCursor: String |
| 56 | endCursor: String |
| 57 | } |
| 58 | |
| 59 | # Queries |
| 60 | type Query { |
| 61 | me: User |
| 62 | user(id: UUID!): User |
| 63 | users(first: Int, after: String, role: UserRole): UserConnection! |
| 64 | order(id: UUID!): Order |
| 65 | orders(status: OrderStatus, first: Int, after: String): OrderConnection! |
| 66 | } |
| 67 | |
| 68 | # Mutations |
| 69 | type Mutation { |
| 70 | createUser(input: CreateUserInput!): CreateUserPayload! |
| 71 | updateUser(id: UUID!, input: UpdateUserInput!): UpdateUserPayload! |
| 72 | deleteUser(id: UUID!): DeletePayload! |
| 73 | createOrder(input: CreateOrderInput!): CreateOrderPayload! |
| 74 | cancelOrder(id: UUID!): CancelOrderPayload! |
| 75 | } |
| 76 | |
| 77 | # Subscriptions |
| 78 | type Subscription { |
| 79 | orderStatusChanged(orderId: UUID!): Order! |
| 80 | newOrder: Order! |
| 81 | } |
| 82 | |
| 83 | # Inputs & Payloads |
| 84 | input CreateUserInput { name: String!; email: String!; role: UserRole } |
| 85 | type CreateUserPayload { user: User; errors: [UserError!] } |
| 86 | type UserError { field: String; message: String! } |
| 87 | ``` |
| 88 | |
| 89 | --- |
| 90 | |
| 91 | ## Resolver Pattern (DataLoader — solves N+1) |
| 92 | |
| 93 | ```javascript |
| 94 | // Without DataLoader: N+1 queries |
| 95 | // With DataLoader: batch all user IDs into one SQL IN(...) |
| 96 | |
| 97 | const userLoader = new DataLoader(async (userIds) => { |
| 98 | const users = await db.query(`SELECT * FROM users WHERE id = ANY($1)`, [userIds]); |
| 99 | // Return in same order as input IDs |
| 100 | return userIds.map(id => users.find(u => u.id === id) || null); |
| 101 | }); |
| 102 | |
| 103 | const resolvers = { |
| 104 | Order: { |
| 105 | user: (order, _, { loaders }) => loaders.user.load(order.userId), |
| 106 | }, |
| 107 | Query: { |
| 108 | orders: async (_, { status, first = 20, after }) => { |
| 109 | return paginatedQuery('orders', { status, first, after }); |
| 110 | } |
| 111 | } |
| 112 | }; |
| 113 | ``` |
| 114 | |
| 115 | --- |
| 116 | |
| 117 | ## Error Handling in GraphQL |
| 118 | |
| 119 | ```json |
| 120 | { |
| 121 | "data": { "createUser": null }, |
| 122 | "errors": [ |
| 123 | { |
| 124 | "message": "Email already in use", |
| 125 | "locations": [{ "line": 2, "column": 3 }], |
| 126 | "path": ["createUser"], |
| 127 | "extensions": { |
| 128 | "code": "USER_EMAIL_TAKEN", |
| 129 | "field": "email" |
| 130 | } |
| 131 | } |
| 132 | ] |
| 133 | } |
| 134 | ``` |
| 135 | |
| 136 | --- |
| 137 | |
| 138 | ## gRPC Proto Definition |
| 139 | |
| 140 | ```protobuf |
| 141 | syntax = "proto3"; |
| 142 | package users.v1; |
| 143 | option go_package = "github.com/example/api/users/v1"; |
| 144 | |
| 145 | import "google/protobuf/timestamp.proto"; |
| 146 | import "google/protobuf/empty.proto"; |
| 147 | |
| 148 | service UsersService { |
| 149 | // Unary RPCs |
| 150 | rpc GetUser(GetUserRequest) returns (User); |
| 151 | rpc CreateUser(CreateUserRequest) returns (User); |
| 152 | rpc UpdateUser(UpdateUserRequest) returns (User); |
| 153 | rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty); |
| 154 | rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); |
| 155 | |
| 156 | // Server streaming |
| 157 | rpc WatchUser(GetUserRequest) returns (stream User); |
| 158 | |
| 159 | // Bidirectional streaming |
| 160 | rpc SyncUsers(stream SyncRequest) returns (stream SyncResponse); |
| 161 | } |
| 162 | |
| 163 | message User { |
| 164 | string id = 1; |
| 165 | string name = 2; |
| 166 | string email = 3; |
| 167 | string role = 4; |
| 168 | google.protobuf.Timestamp created_at = 5; |
| 169 | } |
| 170 | |
| 171 | message GetUserRequest { string id = 1; } |
| 172 | message CreateUserRequest { string name = 1; string email = 2; string role = 3; } |
| 173 | message UpdateUserRequest { string id = 1; string name = 2; string email = 3; } |
| 174 | message DeleteUserRequest { string id = 1; } |
| 175 | message ListUsersRequest { int32 page = 1; int32 limit = 2; string role = 3; } |
| 176 | message ListUsersResponse { repeated User users = 1; int32 total = 2; } |
| 177 | ``` |
| 178 | |
| 179 | --- |
| 180 | |
| 181 | ## REST vs GraphQL vs gRPC Decision Matrix |
| 182 | |
| 183 | | Factor | REST | GraphQL | gRPC | |
| 184 | |--------|----- |