CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-msw

Seamless REST/GraphQL API mocking library for browser and Node.js.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

graphql-handlers.mddocs/

GraphQL Handlers

GraphQL handlers provide powerful interception and mocking of GraphQL operations with support for operation-specific handling, typed responses, and scoped endpoint management.

Capabilities

GraphQL Request Handler Function

Creates handlers for GraphQL operations based on operation name, type, or custom predicates.

/**
 * Creates a GraphQL request handler for specific operations
 * @param predicate - Operation name, DocumentNode, or custom predicate function
 * @param resolver - Function that returns a Response for matching GraphQL operations
 * @param options - Optional configuration including 'once' for single-use handlers
 * @returns GraphQLHandler instance
 */
interface GraphQLRequestHandler {
  <Query extends GraphQLQuery = GraphQLQuery,
   Variables extends GraphQLVariables = GraphQLVariables>(
    predicate: 
      | GraphQLHandlerNameSelector
      | DocumentNode
      | TypedDocumentNode<Query, Variables>
      | GraphQLCustomPredicate,
    resolver: GraphQLResponseResolver<
      [Query] extends [never] ? GraphQLQuery : Query,
      Variables
    >,
    options?: RequestHandlerOptions
  ): GraphQLHandler;
}

type GraphQLHandlerNameSelector = string;

type GraphQLCustomPredicate = (info: {
  query: any;
  variables: Record<string, any>;
  operationType: string;
  operationName?: string;
}) => boolean;

type GraphQLResponseResolver<
  Query extends GraphQLQuery = GraphQLQuery,
  Variables extends GraphQLVariables = GraphQLVariables
> = (info: GraphQLResolverExtras<Variables>) => 
  | Response 
  | Promise<Response>
  | HttpResponse<GraphQLResponseBody<Query>>
  | Promise<HttpResponse<GraphQLResponseBody<Query>>>;

interface GraphQLResolverExtras<Variables> {
  query: any;
  variables: Variables;
  operationType: string;
  operationName?: string;
  request: Request;
}

GraphQL Operation Handlers

The graphql namespace provides handlers for different types of GraphQL operations.

const graphql: {
  /** Handle GraphQL queries by operation name */
  query: GraphQLRequestHandler;
  /** Handle GraphQL mutations by operation name */
  mutation: GraphQLRequestHandler;
  /** Handle any GraphQL operation regardless of type or name */
  operation: <Query, Variables>(
    resolver: GraphQLResponseResolver<Query, Variables>
  ) => GraphQLHandler;
  /** Create scoped GraphQL handlers for a specific endpoint URL */
  link: (url: string) => typeof graphql;
};

Usage Examples:

import { graphql, HttpResponse } from "msw";

// Handle specific query by name
graphql.query('GetUser', ({ variables }) => {
  return HttpResponse.json({
    data: {
      user: {
        id: variables.userId,
        name: 'John Doe',
        email: 'john@example.com'
      }
    }
  });
});

// Handle specific mutation by name
graphql.mutation('CreatePost', ({ variables }) => {
  return HttpResponse.json({
    data: {
      createPost: {
        id: Date.now().toString(),
        title: variables.title,
        content: variables.content,
        author: variables.authorId
      }
    }
  });
});

// Handle any GraphQL operation
graphql.operation(({ query, variables, operationType, operationName }) => {
  console.log(`${operationType}: ${operationName}`, variables);
  
  return HttpResponse.json({
    data: {},
    extensions: {
      tracing: { version: 1, startTime: Date.now() }
    }
  });
});

// Handle operations with errors
graphql.query('GetSecretData', ({ variables }) => {
  return HttpResponse.json({
    errors: [
      {
        message: 'Unauthorized access',
        extensions: {
          code: 'UNAUTHENTICATED',
          path: ['secretData']
        }
      }
    ],
    data: null
  });
});

// Handle operations with partial data and errors
graphql.query('GetUserProfile', ({ variables }) => {
  return HttpResponse.json({
    data: {
      user: {
        id: variables.userId,
        name: 'John Doe',
        email: null // Simulating null field
      }
    },
    errors: [
      {
        message: 'Email field is restricted',
        path: ['user', 'email'],
        extensions: { code: 'FORBIDDEN' }
      }
    ]
  });
});

Scoped GraphQL Handlers

Create handlers for specific GraphQL endpoints using the link method.

/**
 * Creates scoped GraphQL handlers for a specific endpoint URL
 * @param url - The GraphQL endpoint URL to scope handlers to
 * @returns Object with query, mutation, and operation methods scoped to the URL
 */
function link(url: string): {
  query: GraphQLRequestHandler;
  mutation: GraphQLRequestHandler;
  operation: GraphQLRequestHandler;
};

Usage Examples:

// Create scoped handlers for different GraphQL APIs
const githubApi = graphql.link('https://api.github.com/graphql');
const shopifyApi = graphql.link('https://shop.myshopify.com/api/graphql');

// GitHub API handlers
githubApi.query('GetRepository', ({ variables }) => {
  return HttpResponse.json({
    data: {
      repository: {
        name: variables.name,
        owner: { login: variables.owner },
        stargazerCount: 1234
      }
    }
  });
});

// Shopify API handlers  
shopifyApi.query('GetProducts', () => {
  return HttpResponse.json({
    data: {
      products: {
        edges: [
          { node: { id: '1', title: 'Product 1', handle: 'product-1' } }
        ]
      }
    }
  });
});

// Local development API
const localApi = graphql.link('http://localhost:4000/graphql');

localApi.mutation('SignUp', ({ variables }) => {
  return HttpResponse.json({
    data: {
      signUp: {
        token: 'fake-jwt-token',
        user: {
          id: 'new-user-id',
          email: variables.email
        }
      }
    }
  });
});

Typed GraphQL Handlers

Use TypeScript for type-safe GraphQL handlers with DocumentNode or TypedDocumentNode.

interface TypedDocumentNode<
  Result = { [key: string]: any },
  Variables = { [key: string]: any }
> extends DocumentNode {
  __apiType?: (variables: Variables) => Result;
  __resultType?: Result;
  __variablesType?: Variables;
}

Usage Examples:

import { graphql, HttpResponse } from "msw";
import { DocumentNode } from "graphql";

// Using DocumentNode (from graphql-tag, @apollo/client, etc.)
const GET_USER_QUERY: DocumentNode = gql`
  query GetUser($userId: ID!) {
    user(id: $userId) {
      id
      name
      email
    }
  }
`;

graphql.query(GET_USER_QUERY, ({ variables }) => {
  return HttpResponse.json({
    data: {
      user: {
        id: variables.userId,
        name: 'John Doe',
        email: 'john@example.com'
      }
    }
  });
});

// Using TypedDocumentNode for full type safety
interface GetUserQuery {
  user: {
    id: string;
    name: string;
    email: string;
  };
}

interface GetUserVariables {
  userId: string;
}

const TYPED_GET_USER_QUERY: TypedDocumentNode<GetUserQuery, GetUserVariables> = gql`
  query GetUser($userId: ID!) {
    user(id: $userId) {
      id
      name  
      email
    }
  }
`;

graphql.query(TYPED_GET_USER_QUERY, ({ variables }) => {
  // variables is fully typed as GetUserVariables
  // Return type is enforced to match GetUserQuery
  return HttpResponse.json({
    data: {
      user: {
        id: variables.userId, // TypeScript knows this is string
        name: 'John Doe',
        email: 'john@example.com'
      }
    }
  });
});

Custom GraphQL Predicates

Use custom predicate functions for advanced operation matching.

// Match operations by complexity
graphql.operation(({ query, variables }) => {
  return HttpResponse.json({ data: {} });
}, {
  predicate: ({ query, operationType }) => {
    // Custom logic to determine if this handler should match
    return operationType === 'query' && query.definitions.length > 1;
  }
});

// Match operations with specific variables
graphql.query('GetUser', ({ variables }) => {
  return HttpResponse.json({
    data: { user: { id: variables.userId, name: 'Admin User' } }
  });
}, {
  predicate: ({ variables }) => {
    return variables.userId === 'admin';
  }
});

// Match operations by request headers
graphql.operation(({ request }) => {
  const authHeader = request.headers.get('authorization');
  
  if (!authHeader?.startsWith('Bearer ')) {
    return HttpResponse.json({
      errors: [{ message: 'Authentication required' }]
    }, { status: 401 });
  }
  
  return HttpResponse.json({ data: {} });
}, {
  predicate: ({ request }) => {
    return !request.headers.get('authorization');
  }
});

GraphQL Response Formats

Handle different GraphQL response patterns including data, errors, and extensions.

// Successful response with data
graphql.query('GetUser', () => {
  return HttpResponse.json({
    data: {
      user: { id: '1', name: 'John' }
    }
  });
});

// Response with errors
graphql.mutation('DeleteUser', () => {
  return HttpResponse.json({
    errors: [
      {
        message: 'User not found',
        extensions: {
          code: 'USER_NOT_FOUND',
          exception: {
            stacktrace: ['Error: User not found', '  at...']
          }
        },
        locations: [{ line: 2, column: 3 }],
        path: ['deleteUser']
      }
    ],
    data: { deleteUser: null }
  });
});

// Response with extensions
graphql.query('GetPosts', () => {
  return HttpResponse.json({
    data: {
      posts: [{ id: '1', title: 'Hello World' }]
    },
    extensions: {
      tracing: {
        version: 1,
        startTime: '2023-01-01T00:00:00Z',
        endTime: '2023-01-01T00:00:01Z',
        duration: 1000000000
      },
      caching: {
        version: 1,
        hints: [{ path: ['posts'], maxAge: 300 }]
      }
    }
  });
});

// HTTP error response (network/server error)
graphql.operation(() => {
  return HttpResponse.json(
    { error: 'Internal Server Error' },
    { status: 500 }
  );
});

Types

// Handler types
interface GraphQLHandler {
  operationType: GraphQLOperationType;
  predicate: 
    | string 
    | DocumentNode 
    | TypedDocumentNode<any, any>
    | GraphQLCustomPredicate;
  url: string;
  resolver: GraphQLResponseResolver<any, any>;
  options: RequestHandlerOptions;
}

type GraphQLOperationType = 'query' | 'mutation' | 'subscription' | 'all';

// Request types
type GraphQLQuery = Record<string, any>;
type GraphQLVariables = Record<string, any>;

interface GraphQLRequestBody {
  query: string;
  variables?: GraphQLVariables;
  operationName?: string;
}

interface GraphQLJsonRequestBody extends GraphQLRequestBody {
  extensions?: Record<string, any>;
}

// Response types
type GraphQLResponseBody<Query extends GraphQLQuery = GraphQLQuery> = {
  data?: Query;
  errors?: GraphQLError[];
  extensions?: Record<string, any>;
};

interface GraphQLError {
  message: string;
  locations?: Array<{ line: number; column: number }>;
  path?: Array<string | number>;
  extensions?: Record<string, any>;
}

// Operation parsing
interface ParsedGraphQLRequest {
  operationType: string;
  operationName?: string;
  query: any;
  variables: GraphQLVariables;
}

docs

browser-setup.md

cli.md

graphql-handlers.md

http-handlers.md

index.md

nodejs-setup.md

react-native-setup.md

response-creation.md

utilities.md

websocket-handlers.md

tile.json