0
# HTTP Handlers
1
2
HTTP handlers provide Express-like routing for intercepting and mocking HTTP requests with support for path parameters, query strings, and custom predicates.
3
4
## Capabilities
5
6
### HTTP Request Handler Function
7
8
Creates handlers for specific HTTP methods and URL patterns.
9
10
```typescript { .api }
11
/**
12
* Creates an HTTP request handler for a specific method and URL pattern
13
* @param predicate - URL pattern (string/RegExp) or custom predicate function
14
* @param resolver - Function that returns a Response for matching requests
15
* @param options - Optional configuration including 'once' for single-use handlers
16
* @returns HttpHandler instance
17
*/
18
interface HttpRequestHandler {
19
<Params extends PathParams<keyof Params> = PathParams,
20
RequestBodyType extends DefaultBodyType = DefaultBodyType,
21
ResponseBodyType extends DefaultBodyType = undefined>(
22
predicate: HttpRequestPredicate<Params>,
23
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
24
options?: RequestHandlerOptions
25
): HttpHandler;
26
}
27
28
type HttpRequestPredicate<Params> =
29
| string
30
| RegExp
31
| HttpCustomPredicate<Params>;
32
33
type HttpCustomPredicate<Params = Record<string, string>> = (info: {
34
request: Request;
35
parsedResult: HttpRequestParsedResult<Params>;
36
}) => boolean;
37
38
interface HttpRequestParsedResult<Params = Record<string, string>> {
39
match: Match<Params>;
40
query: RequestQuery;
41
}
42
43
type HttpResponseResolver<
44
Params extends PathParams<keyof Params> = PathParams,
45
RequestBodyType extends DefaultBodyType = DefaultBodyType,
46
ResponseBodyType extends DefaultBodyType = DefaultBodyType
47
> = (info: HttpRequestResolverExtras<Params>, request: Request, requestBody: RequestBodyType) =>
48
| Response
49
| Promise<Response>
50
| HttpResponse<ResponseBodyType>
51
| Promise<HttpResponse<ResponseBodyType>>;
52
53
interface HttpRequestResolverExtras<Params> {
54
request: Request;
55
params: Params;
56
cookies: Record<string, string>;
57
}
58
```
59
60
### HTTP Method Handlers
61
62
The `http` namespace provides handlers for all standard HTTP methods.
63
64
```typescript { .api }
65
const http: {
66
/** Handle requests with any HTTP method */
67
all: HttpRequestHandler;
68
/** Handle HTTP GET requests */
69
get: HttpRequestHandler;
70
/** Handle HTTP POST requests */
71
post: HttpRequestHandler;
72
/** Handle HTTP PUT requests */
73
put: HttpRequestHandler;
74
/** Handle HTTP DELETE requests */
75
delete: HttpRequestHandler;
76
/** Handle HTTP PATCH requests */
77
patch: HttpRequestHandler;
78
/** Handle HTTP HEAD requests */
79
head: HttpRequestHandler;
80
/** Handle HTTP OPTIONS requests */
81
options: HttpRequestHandler;
82
};
83
```
84
85
**Usage Examples:**
86
87
```typescript
88
import { http, HttpResponse } from "msw";
89
90
// Basic GET handler with static response
91
http.get('/api/users', () => {
92
return HttpResponse.json([
93
{ id: 1, name: 'John Doe' },
94
{ id: 2, name: 'Jane Smith' }
95
]);
96
});
97
98
// POST handler with request body access
99
http.post('/api/users', async ({ request }) => {
100
const newUser = await request.json();
101
102
return HttpResponse.json(
103
{ ...newUser, id: Date.now() },
104
{ status: 201 }
105
);
106
});
107
108
// Handler with path parameters
109
http.get('/api/users/:userId', ({ params }) => {
110
const { userId } = params;
111
112
return HttpResponse.json({
113
id: userId,
114
name: `User ${userId}`
115
});
116
});
117
118
// Handler with query parameters
119
http.get('/api/search', ({ request }) => {
120
const url = new URL(request.url);
121
const query = url.searchParams.get('q');
122
const limit = url.searchParams.get('limit') || '10';
123
124
return HttpResponse.json({
125
query,
126
results: [],
127
limit: parseInt(limit)
128
});
129
});
130
131
// Handler with cookies
132
http.get('/api/profile', ({ request, cookies }) => {
133
const sessionId = cookies.sessionId;
134
135
if (!sessionId) {
136
return HttpResponse.json(
137
{ error: 'Unauthorized' },
138
{ status: 401 }
139
);
140
}
141
142
return HttpResponse.json({ user: 'profile data' });
143
});
144
145
// RegExp pattern handler
146
http.get(/\/api\/files\/(.+)\.json$/, ({ request }) => {
147
const url = new URL(request.url);
148
const filename = url.pathname.match(/\/api\/files\/(.+)\.json$/)?.[1];
149
150
return HttpResponse.json({ filename, content: {} });
151
});
152
153
// Custom predicate handler
154
http.post('/api/upload', ({ request }) => {
155
const contentType = request.headers.get('content-type');
156
157
if (!contentType?.includes('multipart/form-data')) {
158
return HttpResponse.json(
159
{ error: 'Invalid content type' },
160
{ status: 400 }
161
);
162
}
163
164
return HttpResponse.json({ success: true });
165
}, {
166
predicate: ({ request }) => {
167
return request.headers.get('content-type')?.includes('multipart/form-data') || false;
168
}
169
});
170
171
// One-time handler (auto-removes after first match)
172
http.get('/api/setup', () => {
173
return HttpResponse.json({ initialized: true });
174
}, { once: true });
175
176
// Handler with different response based on request
177
http.all('/api/echo', async ({ request }) => {
178
const method = request.method;
179
const body = method !== 'GET' ? await request.text() : null;
180
181
return HttpResponse.json({
182
method,
183
url: request.url,
184
headers: Object.fromEntries(request.headers.entries()),
185
body
186
});
187
});
188
```
189
190
### Path Parameter Extraction
191
192
MSW automatically extracts path parameters from URL patterns using Express-style syntax.
193
194
```typescript { .api }
195
type PathParams<T extends string = string> = Record<T, string>;
196
197
interface Match<Params = Record<string, string>> {
198
matches: boolean;
199
params: Params;
200
}
201
```
202
203
**Supported Path Patterns:**
204
205
```typescript
206
// Named parameters
207
http.get('/users/:userId', ({ params }) => {
208
// params.userId is available
209
});
210
211
// Multiple parameters
212
http.get('/users/:userId/posts/:postId', ({ params }) => {
213
// params.userId and params.postId are available
214
});
215
216
// Optional parameters
217
http.get('/api/posts/:id?', ({ params }) => {
218
// params.id may be undefined
219
});
220
221
// Wildcard patterns
222
http.get('/assets/*', ({ request }) => {
223
// Matches any path under /assets/
224
});
225
226
// RegExp with capture groups
227
http.get(/^\/api\/v(\d+)\/users\/(\w+)$/, ({ request }) => {
228
const matches = new URL(request.url).pathname.match(/^\/api\/v(\d+)\/users\/(\w+)$/);
229
const version = matches?.[1];
230
const userId = matches?.[2];
231
});
232
```
233
234
### Query Parameter Access
235
236
Access query parameters through the standard URL API in request handlers.
237
238
```typescript
239
http.get('/api/search', ({ request }) => {
240
const url = new URL(request.url);
241
const searchParams = url.searchParams;
242
243
// Get single values
244
const query = searchParams.get('q');
245
const page = searchParams.get('page') || '1';
246
247
// Get all values for a parameter
248
const tags = searchParams.getAll('tag');
249
250
// Check if parameter exists
251
const hasFilter = searchParams.has('filter');
252
253
return HttpResponse.json({
254
query,
255
page: parseInt(page),
256
tags,
257
hasFilter
258
});
259
});
260
```
261
262
### Request Body Access
263
264
Access and parse request bodies using standard Request API methods.
265
266
```typescript
267
// JSON body
268
http.post('/api/data', async ({ request }) => {
269
const jsonData = await request.json();
270
return HttpResponse.json({ received: jsonData });
271
});
272
273
// Form data
274
http.post('/api/form', async ({ request }) => {
275
const formData = await request.formData();
276
const username = formData.get('username');
277
return HttpResponse.text(`Hello ${username}`);
278
});
279
280
// Text body
281
http.post('/api/text', async ({ request }) => {
282
const textData = await request.text();
283
return HttpResponse.text(`Received: ${textData}`);
284
});
285
286
// Binary data
287
http.post('/api/upload', async ({ request }) => {
288
const arrayBuffer = await request.arrayBuffer();
289
return HttpResponse.json({ size: arrayBuffer.byteLength });
290
});
291
292
// Stream body
293
http.post('/api/stream', async ({ request }) => {
294
if (request.body) {
295
const reader = request.body.getReader();
296
// Process stream...
297
}
298
return HttpResponse.json({ processed: true });
299
});
300
```
301
302
### Handler Options
303
304
Configure handler behavior with options object.
305
306
```typescript { .api }
307
interface RequestHandlerOptions {
308
/** Remove handler after first match */
309
once?: boolean;
310
}
311
```
312
313
```typescript
314
// One-time handler
315
http.get('/api/init', () => {
316
return HttpResponse.json({ initialized: true });
317
}, { once: true });
318
```
319
320
## Types
321
322
```typescript { .api }
323
// Handler types
324
interface HttpHandler {
325
method: HttpMethods | RegExp;
326
predicate: HttpRequestPredicate<any>;
327
resolver: HttpResponseResolver<any, any, any>;
328
options: RequestHandlerOptions;
329
}
330
331
enum HttpMethods {
332
GET = 'GET',
333
POST = 'POST',
334
PUT = 'PUT',
335
DELETE = 'DELETE',
336
PATCH = 'PATCH',
337
HEAD = 'HEAD',
338
OPTIONS = 'OPTIONS'
339
}
340
341
// Request types
342
type RequestQuery = Record<string, string | string[]>;
343
344
type DefaultBodyType =
345
| string
346
| number
347
| boolean
348
| null
349
| undefined
350
| ArrayBuffer
351
| Blob
352
| FormData
353
| ReadableStream;
354
355
type DefaultRequestMultipartBody = Record<string, string | File>;
356
357
// Response types
358
type ResponseResolverReturnType<ResponseBodyType> =
359
| Response
360
| HttpResponse<ResponseBodyType>
361
| Promise<Response>
362
| Promise<HttpResponse<ResponseBodyType>>;
363
364
type AsyncResponseResolverReturnType<ResponseBodyType> = Promise<
365
ResponseResolverReturnType<ResponseBodyType>
366
>;
367
```