Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions skills/graphql/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
name: graphql
version: 1.0.0
description: GraphQL API development and interaction skill. Use when building GraphQL schemas, writing queries/mutations, introspecting APIs, or debugging GraphQL endpoints.
argument-hint: "[query|mutation|introspect] [endpoint] [--variables]"
---

# GraphQL Skill

Build, query, and debug GraphQL APIs effectively.

## Quick Reference

```bash
# Introspect a GraphQL endpoint
curl -X POST <endpoint> \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name } } }"}'

# Execute a query
curl -X POST <endpoint> \
-H "Content-Type: application/json" \
-d '{"query": "query { users { id name } }"}'

# Execute a mutation with variables
curl -X POST <endpoint> \
-H "Content-Type: application/json" \
-d '{"query": "mutation($input: CreateUserInput!) { createUser(input: $input) { id } }", "variables": {"input": {"name": "John"}}}'
```

## Schema Design Patterns

### Type Definitions
```graphql
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}

type Post {
id: ID!
title: String!
content: String!
author: User!
}

input CreateUserInput {
name: String!
email: String!
}

type Query {
user(id: ID!): User
users(first: Int, after: String): UserConnection!
}

type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
```

### Pagination (Relay-style)
```graphql
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}

type UserEdge {
node: User!
cursor: String!
}

type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
```

## Best Practices

| Practice | Description |
|----------|-------------|
| **Nullable by default** | Only use `!` when field is guaranteed non-null |
| **Use Input types** | Wrap mutation arguments in Input types |
| **Descriptive names** | `createUser`, not `addUser` or `newUser` |
| **Relay pagination** | Use Connection pattern for lists |
| **Error handling** | Use union types for error states |
| **Batching** | Use DataLoader to prevent N+1 queries |

## Error Handling Pattern

```graphql
union CreateUserResult = User | ValidationError | AuthError

type ValidationError {
field: String!
message: String!
}

type AuthError {
message: String!
}

type Mutation {
createUser(input: CreateUserInput!): CreateUserResult!
}
```

## References

| Topic | File |
|-------|------|
| Introspection | [introspection.md](references/introspection.md) |
| Subscriptions | [subscriptions.md](references/subscriptions.md) |
| Authentication | [auth.md](references/auth.md) |
| Performance | [performance.md](references/performance.md) |
155 changes: 155 additions & 0 deletions skills/graphql/references/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# GraphQL Authentication & Authorization

## Authentication Methods

### Bearer Token (JWT)

```bash
curl -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-d '{"query": "{ me { id name } }"}'
```

### API Key

```bash
curl -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"query": "{ users { id } }"}'
```

## Server-Side Context

```javascript
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');

if (token) {
try {
const user = await verifyToken(token);
return { user };
} catch (e) {
// Invalid token - continue as unauthenticated
}
}

return { user: null };
},
});
```

## Authorization Patterns

### Field-Level Authorization

```javascript
const resolvers = {
User: {
email: (user, _, { currentUser }) => {
// Only show email to the user themselves or admins
if (currentUser?.id === user.id || currentUser?.role === 'ADMIN') {
return user.email;
}
return null;
},
},
};
```

### Directive-Based

```graphql
directive @auth(requires: Role = USER) on FIELD_DEFINITION

enum Role {
ADMIN
USER
GUEST
}

type Query {
users: [User!]! @auth(requires: ADMIN)
me: User @auth(requires: USER)
publicPosts: [Post!]!
}
```

```javascript
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { requires } = this.args;
const originalResolve = field.resolve;

field.resolve = async function (...args) {
const context = args[2];

if (!context.user) {
throw new AuthenticationError('Not authenticated');
}

if (!hasRole(context.user, requires)) {
throw new ForbiddenError('Not authorized');
}

return originalResolve.apply(this, args);
};
}
}
```

### Shield Library

```javascript
import { shield, rule, allow, deny } from 'graphql-shield';

const isAuthenticated = rule()((parent, args, { user }) => !!user);
const isAdmin = rule()((parent, args, { user }) => user?.role === 'ADMIN');
const isOwner = rule()((parent, args, { user }) => parent.userId === user?.id);

const permissions = shield({
Query: {
users: isAdmin,
me: isAuthenticated,
publicPosts: allow,
},
Mutation: {
createPost: isAuthenticated,
deleteUser: isAdmin,
},
User: {
email: or(isOwner, isAdmin),
},
});
```

## Error Handling

```javascript
import { AuthenticationError, ForbiddenError } from 'apollo-server';

// In resolvers
if (!context.user) {
throw new AuthenticationError('You must be logged in');
}

if (context.user.role !== 'ADMIN') {
throw new ForbiddenError('Admin access required');
}
```

Response format:
```json
{
"errors": [{
"message": "You must be logged in",
"extensions": {
"code": "UNAUTHENTICATED"
}
}]
}
```
Loading