Skip to content
Back to skills

167 Typescript 9afe8b91

ASecurity

```typescript import { GraphQLResolveInfo } from 'graphql';

  • 9 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added October 11, 2026
content-marketingtypescriptnodeexpresstestingdatabase

Works with

  • cursor
  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 167-typescript_9afe8b91 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 167 Typescript 9afe8b91?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 167 Typescript 9afe8b91
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-167-typescript-9afe8b91/badge)](https://www.skillsdirectory.com/skills/tools-only-167-typescript-9afe8b91)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# GraphQL Resolvers

## Basic Resolver Pattern

```typescript
import { GraphQLResolveInfo } from 'graphql';

// Resolver signature
type Resolver<TSource, TArgs, TContext, TReturn> = (
  parent: TSource,
  args: TArgs,
  context: TContext,
  info: GraphQLResolveInfo
) => Promise<TReturn> | TReturn;

// User resolvers
const resolvers = {
  Query: {
    user: async (
      parent,
      args: { id: string },
      context: Context
    ): Promise<User | null> => {
      return context.dataSources.users.findById(args.id);
    },

    users: async (
      parent,
      args: { first?: number; after?: string },
      context: Context
    ): Promise<User[]> => {
      return context.dataSources.users.findAll(args);
    },
  },

  Mutation: {
    createUser: async (
      parent,
      args: { input: CreateUserInput },
      context: Context
    ): Promise<User> => {
      if (!context.user) {
        throw new Error('Unauthorized');
      }
      return context.dataSources.users.create(args.input);
    },
  },
};
```

## Context Setup

```typescript
import { Request } from 'express';
import { User } from './models';
import { DataSources } from './datasources';

export interface Context {
  user: User | null;
  dataSources: DataSources;
  loaders: Loaders;
  req: Request;
  authToken: string | null;
}

// Apollo Server context
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }): Promise<Context> => {
    // Extract auth token
    const authToken = req.headers.authorization?.replace('Bearer ', '') || null;

    // Verify user
    let user: User | null = null;
    if (authToken) {
      user = await verifyToken(authToken);
    }

    // Create data sources
    const dataSources = new DataSources({
      db: prisma,
      redis: redisClient,
    });

    // Create DataLoaders
    const loaders = createLoaders(dataSources);

    return {
      user,
      dataSources,
      loaders,
      req,
      authToken,
    };
  },
});
```

## DataLoader for N+1 Prevention

```typescript
import DataLoader from 'dataloader';

// Create loaders
export function createLoaders(dataSources: DataSources): Loaders {
  return {
    userLoader: new DataLoader<string, User>(
      async (ids: readonly string[]) => {
        const users = await dataSources.users.findByIds([...ids]);
        // Return in same order as input ids
        return ids.map(id => users.find(u => u.id === id) || null);
      },
      {
        cache: true,
        batchScheduleFn: (callback) => setTimeout(callback, 10),
      }
    ),

    postsByAuthorLoader: new DataLoader<string, Post[]>(
      async (authorIds: readonly string[]) => {
        const posts = await dataSources.posts.findByAuthorIds([...authorIds]);
        // Group by author
        return authorIds.map(authorId =>
          posts.filter(p => p.authorId === authorId)
        );
      }
    ),
  };
}

// Field resolver using DataLoader
const resolvers = {
  Post: {
    author: async (
      post: Post,
      args,
      context: Context
    ): Promise<User> => {
      // Batches multiple requests into single DB query
      return context.loaders.userLoader.load(post.authorId);
    },
  },

  User: {
    posts: async (
      user: User,
      args,
      context: Context
    ): Promise<Post[]> => {
      return context.loaders.postsByAuthorLoader.load(user.id);
    },
  },
};
```

## Field Resolvers

```typescript
const resolvers = {
  User: {
    // Simple field resolver
    fullName: (user: User): string => {
      return `${user.firstName} ${user.lastName}`;
    },

    // Async field resolver with DB query
    postCount: async (
      user: User,
      args,
      context: Context
    ): Promise<number> => {
      return context.dataSources.posts.countByAuthor(user.id);
    },

    // Field resolver with arguments
    posts: async (
      user: User,
      args: { first?: number; status?: PostStatus },
      context: Context
    ): Promise<Post[]> => {
      return context.dataSources.posts.findByAuthor(user.id, {
        limit: args.first,
        status: args.status,
      });
    },

    // Nullable field with conditional logic
    profile: async (
      user: User,
      args,
      context: Context
    ): Promise<Profile | null> => {
      if (!user.hasProfile) return null;
      return context.loaders.profileLoader.load(user.id);
    },
  },
};
```

## Interface Resolvers

```typescript
const resolvers = {
  // Interface type resolver
  Searchable: {
    __resolveType(obj: Article | Video | Podcast): string {
      if ('content' in obj) return 'Article';
      if ('duration' in obj) return 'Video';
      if ('audioUrl' in obj) return 'Podcast';
      throw new Error('Unknown Searchable type');
    },
  },

  // Common interface fields (shared resolvers)
  Article: {
    id: (article: Article) => article.id,
    title: (article: Article) => article.title,
    description: (article: Article) => article.description,
  },

  Video: {
    id: (video: Video) => video.id,
    title: (video: Video) => video.title,
    description: (video: Video) => video.description,
  },
};
```

## Union Resolvers

```typescript
const resolvers = {
  // Union type resolver
  SearchResult: {
    __resolveType(
      obj: Article | Video | Podcast,
      context: Context,
      info: GraphQLResolveInfo
    ): string {
      if ('content' in obj) return 'Article';
      if ('duration' in obj && 'url' in obj) return 'Video';
      if ('audioUrl' in obj) return 'Podcast';
      throw new Error('Unknown SearchResult type');
    },
  },

  Query: {
    searchContent: async (
      parent,
      args: { query: string },
      context: Context
    ): Promise<(Article | Video | Podcast)[]> => {
      // Return mixed array of different types
      const [articles, videos, podcasts] = await Promise.all([
        context.dataSources.articles.search(args.query),
        context.dataSources.videos.search(args.query),
        context.dataSources.podcasts.search(args.query),
      ]);
      return [...articles, ...videos, ...podcasts];
    },
  },
};
```

## Error Handling

```typescript
import { GraphQLError } from 'graphql';
import { ApolloServerErrorCode } from '@apollo/server/errors';

const resolvers = {
  Query: {
    user: async (
      parent,
      args: { id: string },
      context: Context
    ): Promise<User> => {
      const user = await context.dataSources.users.findById(args.id);

      if (!user) {
        throw new GraphQLError('User not found', {
          extensions: {
            code: 'USER_NOT_FOUND',
            http: { status: 404 },
            userId: args.id,
          },
        });
      }

      return user;
    },
  },

  Mutation: {
    updateUser: async (
      parent,
      args: { id: string; input: UpdateUserInput },
      context: Context
    ): Promise<User> => {
      // Check authentication
      if (!context.user) {
        throw new GraphQLError('Unauthorized', {
          extensions: {
            code: ApolloServerErrorCode.UNAUTHENTICATED,
            http: { status: 401 },
          },
        });
      }

      // Check authorization
      if (context.user.id !== args.id && !context.user.isAdmin) {
        throw new GraphQLError('Forbidden', {
          extensions: {
            code: ApolloServerErrorCode.FORBIDDEN,
            http: { status: 403 },
          },
        });
      }

      try {
        return await context.dataSources.users.update(args.id, args.input);
      } catch (error) {
        throw new GraphQLError('Failed to update user', {
          extensions: {
            code: 'UPDATE_FAILED',
            originalError: error,
          },
        });
      }
    },
  },
};
```

## Pagination Resolvers

```typescript
import { encodeCursor, decodeCursor } from './utils/cursor';

const resolvers = {
  Query: {
    posts: async (
      parent,
      args: { first?: number; after?: string },
      context: Context
    ): Promise<PostConnection> => {
      const limit = Math.min(args.first || 10, 100);
      const cursor = args.after ? decodeCursor(args.after) : null;

      // Fetch one extra to determine hasNextPage
      const posts = await context.dataSources.posts.findAll({
        limit: limit + 1,
        cursor,
      });

      const hasNextPage = posts.length > limit;
      const edges = posts.slice(0, limit).map(post => ({
        node: post,
        cursor: encodeCursor(post.id),
      }));

      return {
        edges,
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!cursor,
          startCursor: edges[0]?.cursor || null,
          endCursor: edges[edges.length - 1]?.cursor || null,
        },
        totalCount: await context.dataSources.posts.count(),
      };
    },
  },
};
```

## Batching Patterns

```typescript
// Batch multiple queries
class UserDataSource {
  private db: PrismaClient;

  async findByIds(ids: string[]): Promise<User[]> {
    // Single query instead of N queries
    return this.db.user.findMany({
      where: { id: { in: ids } },
    });
  }

  async findByEmails(emails: string[]): Promise<User[]> {
    return this.db.user.findMany({
      where: { email: { in: emails } },
    });
  }
}

// DataLoader with caching
const userLoader = new DataLoader<string, User>(
  async (ids) => {
    console.log('Batching user queries:', ids.length);
    const users = await dataSources.users.findByIds([...ids]);
    return ids.map(id => users.find(u => u.id === id) || null);
  },
  {
    cache: true,
    maxBatchSize: 100,
    batchScheduleFn: (callback) => setTimeout(callback, 10),
  }
);
```

## Resolver Best Practices

1. **Use DataLoader**: Always batch and cache database queries
2. **Avoid N+1**: Use DataLoader for all foreign key relationships
3. **Type Safety**: Use TypeScript for resolver type safety
4. **Error Handling**: Throw GraphQLError with proper codes and extensions
5. **Authorization**: Check permissions in resolvers, not data sources
6. **Pagination**: Implement cursor-based pagination for lists
7. **Context**: Keep context creation lightweight
8. **Caching**: Use DataLoader caching per request
9. **Batching**: Batch queries with DataLoader or in data source
10. **Testing**: Unit test resolvers with mocked context

Files in this skill

  • README.md863 B
  • skill.md10 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.