graphql — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited graphql (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server.
This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.
2025 lesson: GraphQL isn't always the answer. For simple CRUD, REST is simpler. For high-performance public APIs, REST with caching wins. Use GraphQL when you have complex data relationships and diverse client needs.
Type-safe schema with proper nullability
When to use: Designing any GraphQL API
""" The schema is your API contract. Design nullability intentionally - non-null fields must always resolve. """
type Query {
user(id: ID!): User!
userByEmail(email: String!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
searchUsers( query: String! first: Int after: String ): UserConnection! }
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload! updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload! deleteUser(id: ID!): DeleteUserPayload! }
type Subscription { userCreated: User! messageReceived(roomId: ID!): Message! }
input CreateUserInput { email: String! name: String! role: Role = USER }
input UpdateUserInput { email: String name: String role: Role }
type CreateUserPayload { user: User errors: [Error!]! }
union UpdateUserPayload = UpdateUserSuccess | NotFoundError | ValidationError
type UpdateUserSuccess { user: User! }
enum Role { USER ADMIN MODERATOR }
type User { id: ID! email: String! name: String! role: Role! posts(limit: Int = 10): [Post!]! createdAt: DateTime! }
type Post { id: ID! title: String! content: String! author: User! comments: [Comment!]! published: Boolean! }
type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! }
type UserEdge { node: User! cursor: String! }
type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }
Batch and cache database queries
When to use: Resolving relationships
""" Without DataLoader, fetching 10 posts with authors makes 11 queries (1 for posts + 10 for each author). DataLoader batches into 2 queries. """
import DataLoader from 'dataloader';
// Create loaders per request function createLoaders(db) { return { userLoader: new DataLoader(async (ids) => { // Single query for all users const users = await db.user.findMany({ where: { id: { in: ids } } });
// Return in same order as ids const userMap = new Map(users.map(u => [u.id, u])); return ids.map(id => userMap.get(id) || null); }),
postsByAuthorLoader: new DataLoader(async (authorIds) => { const posts = await db.post.findMany({ where: { authorId: { in: authorIds } } });
// Group by author const postsByAuthor = new Map(); posts.forEach(post => { const existing = postsByAuthor.get(post.authorId) || [];
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.