or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-setup.mdcli.mdgraphql-handlers.mdhttp-handlers.mdindex.mdnodejs-setup.mdreact-native-setup.mdresponse-creation.mdutilities.mdwebsocket-handlers.md

graphql-handlers.mddocs/

0

# GraphQL Handlers

1

2

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

3

4

## Capabilities

5

6

### GraphQL Request Handler Function

7

8

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

9

10

```typescript { .api }

11

/**

12

* Creates a GraphQL request handler for specific operations

13

* @param predicate - Operation name, DocumentNode, or custom predicate function

14

* @param resolver - Function that returns a Response for matching GraphQL operations

15

* @param options - Optional configuration including 'once' for single-use handlers

16

* @returns GraphQLHandler instance

17

*/

18

interface GraphQLRequestHandler {

19

<Query extends GraphQLQuery = GraphQLQuery,

20

Variables extends GraphQLVariables = GraphQLVariables>(

21

predicate:

22

| GraphQLHandlerNameSelector

23

| DocumentNode

24

| TypedDocumentNode<Query, Variables>

25

| GraphQLCustomPredicate,

26

resolver: GraphQLResponseResolver<

27

[Query] extends [never] ? GraphQLQuery : Query,

28

Variables

29

>,

30

options?: RequestHandlerOptions

31

): GraphQLHandler;

32

}

33

34

type GraphQLHandlerNameSelector = string;

35

36

type GraphQLCustomPredicate = (info: {

37

query: any;

38

variables: Record<string, any>;

39

operationType: string;

40

operationName?: string;

41

}) => boolean;

42

43

type GraphQLResponseResolver<

44

Query extends GraphQLQuery = GraphQLQuery,

45

Variables extends GraphQLVariables = GraphQLVariables

46

> = (info: GraphQLResolverExtras<Variables>) =>

47

| Response

48

| Promise<Response>

49

| HttpResponse<GraphQLResponseBody<Query>>

50

| Promise<HttpResponse<GraphQLResponseBody<Query>>>;

51

52

interface GraphQLResolverExtras<Variables> {

53

query: any;

54

variables: Variables;

55

operationType: string;

56

operationName?: string;

57

request: Request;

58

}

59

```

60

61

### GraphQL Operation Handlers

62

63

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

64

65

```typescript { .api }

66

const graphql: {

67

/** Handle GraphQL queries by operation name */

68

query: GraphQLRequestHandler;

69

/** Handle GraphQL mutations by operation name */

70

mutation: GraphQLRequestHandler;

71

/** Handle any GraphQL operation regardless of type or name */

72

operation: <Query, Variables>(

73

resolver: GraphQLResponseResolver<Query, Variables>

74

) => GraphQLHandler;

75

/** Create scoped GraphQL handlers for a specific endpoint URL */

76

link: (url: string) => typeof graphql;

77

};

78

```

79

80

**Usage Examples:**

81

82

```typescript

83

import { graphql, HttpResponse } from "msw";

84

85

// Handle specific query by name

86

graphql.query('GetUser', ({ variables }) => {

87

return HttpResponse.json({

88

data: {

89

user: {

90

id: variables.userId,

91

name: 'John Doe',

92

email: 'john@example.com'

93

}

94

}

95

});

96

});

97

98

// Handle specific mutation by name

99

graphql.mutation('CreatePost', ({ variables }) => {

100

return HttpResponse.json({

101

data: {

102

createPost: {

103

id: Date.now().toString(),

104

title: variables.title,

105

content: variables.content,

106

author: variables.authorId

107

}

108

}

109

});

110

});

111

112

// Handle any GraphQL operation

113

graphql.operation(({ query, variables, operationType, operationName }) => {

114

console.log(`${operationType}: ${operationName}`, variables);

115

116

return HttpResponse.json({

117

data: {},

118

extensions: {

119

tracing: { version: 1, startTime: Date.now() }

120

}

121

});

122

});

123

124

// Handle operations with errors

125

graphql.query('GetSecretData', ({ variables }) => {

126

return HttpResponse.json({

127

errors: [

128

{

129

message: 'Unauthorized access',

130

extensions: {

131

code: 'UNAUTHENTICATED',

132

path: ['secretData']

133

}

134

}

135

],

136

data: null

137

});

138

});

139

140

// Handle operations with partial data and errors

141

graphql.query('GetUserProfile', ({ variables }) => {

142

return HttpResponse.json({

143

data: {

144

user: {

145

id: variables.userId,

146

name: 'John Doe',

147

email: null // Simulating null field

148

}

149

},

150

errors: [

151

{

152

message: 'Email field is restricted',

153

path: ['user', 'email'],

154

extensions: { code: 'FORBIDDEN' }

155

}

156

]

157

});

158

});

159

```

160

161

### Scoped GraphQL Handlers

162

163

Create handlers for specific GraphQL endpoints using the `link` method.

164

165

```typescript { .api }

166

/**

167

* Creates scoped GraphQL handlers for a specific endpoint URL

168

* @param url - The GraphQL endpoint URL to scope handlers to

169

* @returns Object with query, mutation, and operation methods scoped to the URL

170

*/

171

function link(url: string): {

172

query: GraphQLRequestHandler;

173

mutation: GraphQLRequestHandler;

174

operation: GraphQLRequestHandler;

175

};

176

```

177

178

**Usage Examples:**

179

180

```typescript

181

// Create scoped handlers for different GraphQL APIs

182

const githubApi = graphql.link('https://api.github.com/graphql');

183

const shopifyApi = graphql.link('https://shop.myshopify.com/api/graphql');

184

185

// GitHub API handlers

186

githubApi.query('GetRepository', ({ variables }) => {

187

return HttpResponse.json({

188

data: {

189

repository: {

190

name: variables.name,

191

owner: { login: variables.owner },

192

stargazerCount: 1234

193

}

194

}

195

});

196

});

197

198

// Shopify API handlers

199

shopifyApi.query('GetProducts', () => {

200

return HttpResponse.json({

201

data: {

202

products: {

203

edges: [

204

{ node: { id: '1', title: 'Product 1', handle: 'product-1' } }

205

]

206

}

207

}

208

});

209

});

210

211

// Local development API

212

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

213

214

localApi.mutation('SignUp', ({ variables }) => {

215

return HttpResponse.json({

216

data: {

217

signUp: {

218

token: 'fake-jwt-token',

219

user: {

220

id: 'new-user-id',

221

email: variables.email

222

}

223

}

224

}

225

});

226

});

227

```

228

229

### Typed GraphQL Handlers

230

231

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

232

233

```typescript { .api }

234

interface TypedDocumentNode<

235

Result = { [key: string]: any },

236

Variables = { [key: string]: any }

237

> extends DocumentNode {

238

__apiType?: (variables: Variables) => Result;

239

__resultType?: Result;

240

__variablesType?: Variables;

241

}

242

```

243

244

**Usage Examples:**

245

246

```typescript

247

import { graphql, HttpResponse } from "msw";

248

import { DocumentNode } from "graphql";

249

250

// Using DocumentNode (from graphql-tag, @apollo/client, etc.)

251

const GET_USER_QUERY: DocumentNode = gql`

252

query GetUser($userId: ID!) {

253

user(id: $userId) {

254

id

255

name

256

email

257

}

258

}

259

`;

260

261

graphql.query(GET_USER_QUERY, ({ variables }) => {

262

return HttpResponse.json({

263

data: {

264

user: {

265

id: variables.userId,

266

name: 'John Doe',

267

email: 'john@example.com'

268

}

269

}

270

});

271

});

272

273

// Using TypedDocumentNode for full type safety

274

interface GetUserQuery {

275

user: {

276

id: string;

277

name: string;

278

email: string;

279

};

280

}

281

282

interface GetUserVariables {

283

userId: string;

284

}

285

286

const TYPED_GET_USER_QUERY: TypedDocumentNode<GetUserQuery, GetUserVariables> = gql`

287

query GetUser($userId: ID!) {

288

user(id: $userId) {

289

id

290

name

291

email

292

}

293

}

294

`;

295

296

graphql.query(TYPED_GET_USER_QUERY, ({ variables }) => {

297

// variables is fully typed as GetUserVariables

298

// Return type is enforced to match GetUserQuery

299

return HttpResponse.json({

300

data: {

301

user: {

302

id: variables.userId, // TypeScript knows this is string

303

name: 'John Doe',

304

email: 'john@example.com'

305

}

306

}

307

});

308

});

309

```

310

311

### Custom GraphQL Predicates

312

313

Use custom predicate functions for advanced operation matching.

314

315

```typescript

316

// Match operations by complexity

317

graphql.operation(({ query, variables }) => {

318

return HttpResponse.json({ data: {} });

319

}, {

320

predicate: ({ query, operationType }) => {

321

// Custom logic to determine if this handler should match

322

return operationType === 'query' && query.definitions.length > 1;

323

}

324

});

325

326

// Match operations with specific variables

327

graphql.query('GetUser', ({ variables }) => {

328

return HttpResponse.json({

329

data: { user: { id: variables.userId, name: 'Admin User' } }

330

});

331

}, {

332

predicate: ({ variables }) => {

333

return variables.userId === 'admin';

334

}

335

});

336

337

// Match operations by request headers

338

graphql.operation(({ request }) => {

339

const authHeader = request.headers.get('authorization');

340

341

if (!authHeader?.startsWith('Bearer ')) {

342

return HttpResponse.json({

343

errors: [{ message: 'Authentication required' }]

344

}, { status: 401 });

345

}

346

347

return HttpResponse.json({ data: {} });

348

}, {

349

predicate: ({ request }) => {

350

return !request.headers.get('authorization');

351

}

352

});

353

```

354

355

### GraphQL Response Formats

356

357

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

358

359

```typescript

360

// Successful response with data

361

graphql.query('GetUser', () => {

362

return HttpResponse.json({

363

data: {

364

user: { id: '1', name: 'John' }

365

}

366

});

367

});

368

369

// Response with errors

370

graphql.mutation('DeleteUser', () => {

371

return HttpResponse.json({

372

errors: [

373

{

374

message: 'User not found',

375

extensions: {

376

code: 'USER_NOT_FOUND',

377

exception: {

378

stacktrace: ['Error: User not found', ' at...']

379

}

380

},

381

locations: [{ line: 2, column: 3 }],

382

path: ['deleteUser']

383

}

384

],

385

data: { deleteUser: null }

386

});

387

});

388

389

// Response with extensions

390

graphql.query('GetPosts', () => {

391

return HttpResponse.json({

392

data: {

393

posts: [{ id: '1', title: 'Hello World' }]

394

},

395

extensions: {

396

tracing: {

397

version: 1,

398

startTime: '2023-01-01T00:00:00Z',

399

endTime: '2023-01-01T00:00:01Z',

400

duration: 1000000000

401

},

402

caching: {

403

version: 1,

404

hints: [{ path: ['posts'], maxAge: 300 }]

405

}

406

}

407

});

408

});

409

410

// HTTP error response (network/server error)

411

graphql.operation(() => {

412

return HttpResponse.json(

413

{ error: 'Internal Server Error' },

414

{ status: 500 }

415

);

416

});

417

```

418

419

## Types

420

421

```typescript { .api }

422

// Handler types

423

interface GraphQLHandler {

424

operationType: GraphQLOperationType;

425

predicate:

426

| string

427

| DocumentNode

428

| TypedDocumentNode<any, any>

429

| GraphQLCustomPredicate;

430

url: string;

431

resolver: GraphQLResponseResolver<any, any>;

432

options: RequestHandlerOptions;

433

}

434

435

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

436

437

// Request types

438

type GraphQLQuery = Record<string, any>;

439

type GraphQLVariables = Record<string, any>;

440

441

interface GraphQLRequestBody {

442

query: string;

443

variables?: GraphQLVariables;

444

operationName?: string;

445

}

446

447

interface GraphQLJsonRequestBody extends GraphQLRequestBody {

448

extensions?: Record<string, any>;

449

}

450

451

// Response types

452

type GraphQLResponseBody<Query extends GraphQLQuery = GraphQLQuery> = {

453

data?: Query;

454

errors?: GraphQLError[];

455

extensions?: Record<string, any>;

456

};

457

458

interface GraphQLError {

459

message: string;

460

locations?: Array<{ line: number; column: number }>;

461

path?: Array<string | number>;

462

extensions?: Record<string, any>;

463

}

464

465

// Operation parsing

466

interface ParsedGraphQLRequest {

467

operationType: string;

468

operationName?: string;

469

query: any;

470

variables: GraphQLVariables;

471

}

472

```