Seamless REST/GraphQL API mocking library for browser and Node.js.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
HTTP handlers provide Express-like routing for intercepting and mocking HTTP requests with support for path parameters, query strings, and custom predicates.
Creates handlers for specific HTTP methods and URL patterns.
/**
* Creates an HTTP request handler for a specific method and URL pattern
* @param predicate - URL pattern (string/RegExp) or custom predicate function
* @param resolver - Function that returns a Response for matching requests
* @param options - Optional configuration including 'once' for single-use handlers
* @returns HttpHandler instance
*/
interface HttpRequestHandler {
<Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = undefined>(
predicate: HttpRequestPredicate<Params>,
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
options?: RequestHandlerOptions
): HttpHandler;
}
type HttpRequestPredicate<Params> =
| string
| RegExp
| HttpCustomPredicate<Params>;
type HttpCustomPredicate<Params = Record<string, string>> = (info: {
request: Request;
parsedResult: HttpRequestParsedResult<Params>;
}) => boolean;
interface HttpRequestParsedResult<Params = Record<string, string>> {
match: Match<Params>;
query: RequestQuery;
}
type HttpResponseResolver<
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = DefaultBodyType
> = (info: HttpRequestResolverExtras<Params>, request: Request, requestBody: RequestBodyType) =>
| Response
| Promise<Response>
| HttpResponse<ResponseBodyType>
| Promise<HttpResponse<ResponseBodyType>>;
interface HttpRequestResolverExtras<Params> {
request: Request;
params: Params;
cookies: Record<string, string>;
}The http namespace provides handlers for all standard HTTP methods.
const http: {
/** Handle requests with any HTTP method */
all: HttpRequestHandler;
/** Handle HTTP GET requests */
get: HttpRequestHandler;
/** Handle HTTP POST requests */
post: HttpRequestHandler;
/** Handle HTTP PUT requests */
put: HttpRequestHandler;
/** Handle HTTP DELETE requests */
delete: HttpRequestHandler;
/** Handle HTTP PATCH requests */
patch: HttpRequestHandler;
/** Handle HTTP HEAD requests */
head: HttpRequestHandler;
/** Handle HTTP OPTIONS requests */
options: HttpRequestHandler;
};Usage Examples:
import { http, HttpResponse } from "msw";
// Basic GET handler with static response
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
]);
});
// POST handler with request body access
http.post('/api/users', async ({ request }) => {
const newUser = await request.json();
return HttpResponse.json(
{ ...newUser, id: Date.now() },
{ status: 201 }
);
});
// Handler with path parameters
http.get('/api/users/:userId', ({ params }) => {
const { userId } = params;
return HttpResponse.json({
id: userId,
name: `User ${userId}`
});
});
// Handler with query parameters
http.get('/api/search', ({ request }) => {
const url = new URL(request.url);
const query = url.searchParams.get('q');
const limit = url.searchParams.get('limit') || '10';
return HttpResponse.json({
query,
results: [],
limit: parseInt(limit)
});
});
// Handler with cookies
http.get('/api/profile', ({ request, cookies }) => {
const sessionId = cookies.sessionId;
if (!sessionId) {
return HttpResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
return HttpResponse.json({ user: 'profile data' });
});
// RegExp pattern handler
http.get(/\/api\/files\/(.+)\.json$/, ({ request }) => {
const url = new URL(request.url);
const filename = url.pathname.match(/\/api\/files\/(.+)\.json$/)?.[1];
return HttpResponse.json({ filename, content: {} });
});
// Custom predicate handler
http.post('/api/upload', ({ request }) => {
const contentType = request.headers.get('content-type');
if (!contentType?.includes('multipart/form-data')) {
return HttpResponse.json(
{ error: 'Invalid content type' },
{ status: 400 }
);
}
return HttpResponse.json({ success: true });
}, {
predicate: ({ request }) => {
return request.headers.get('content-type')?.includes('multipart/form-data') || false;
}
});
// One-time handler (auto-removes after first match)
http.get('/api/setup', () => {
return HttpResponse.json({ initialized: true });
}, { once: true });
// Handler with different response based on request
http.all('/api/echo', async ({ request }) => {
const method = request.method;
const body = method !== 'GET' ? await request.text() : null;
return HttpResponse.json({
method,
url: request.url,
headers: Object.fromEntries(request.headers.entries()),
body
});
});MSW automatically extracts path parameters from URL patterns using Express-style syntax.
type PathParams<T extends string = string> = Record<T, string>;
interface Match<Params = Record<string, string>> {
matches: boolean;
params: Params;
}Supported Path Patterns:
// Named parameters
http.get('/users/:userId', ({ params }) => {
// params.userId is available
});
// Multiple parameters
http.get('/users/:userId/posts/:postId', ({ params }) => {
// params.userId and params.postId are available
});
// Optional parameters
http.get('/api/posts/:id?', ({ params }) => {
// params.id may be undefined
});
// Wildcard patterns
http.get('/assets/*', ({ request }) => {
// Matches any path under /assets/
});
// RegExp with capture groups
http.get(/^\/api\/v(\d+)\/users\/(\w+)$/, ({ request }) => {
const matches = new URL(request.url).pathname.match(/^\/api\/v(\d+)\/users\/(\w+)$/);
const version = matches?.[1];
const userId = matches?.[2];
});Access query parameters through the standard URL API in request handlers.
http.get('/api/search', ({ request }) => {
const url = new URL(request.url);
const searchParams = url.searchParams;
// Get single values
const query = searchParams.get('q');
const page = searchParams.get('page') || '1';
// Get all values for a parameter
const tags = searchParams.getAll('tag');
// Check if parameter exists
const hasFilter = searchParams.has('filter');
return HttpResponse.json({
query,
page: parseInt(page),
tags,
hasFilter
});
});Access and parse request bodies using standard Request API methods.
// JSON body
http.post('/api/data', async ({ request }) => {
const jsonData = await request.json();
return HttpResponse.json({ received: jsonData });
});
// Form data
http.post('/api/form', async ({ request }) => {
const formData = await request.formData();
const username = formData.get('username');
return HttpResponse.text(`Hello ${username}`);
});
// Text body
http.post('/api/text', async ({ request }) => {
const textData = await request.text();
return HttpResponse.text(`Received: ${textData}`);
});
// Binary data
http.post('/api/upload', async ({ request }) => {
const arrayBuffer = await request.arrayBuffer();
return HttpResponse.json({ size: arrayBuffer.byteLength });
});
// Stream body
http.post('/api/stream', async ({ request }) => {
if (request.body) {
const reader = request.body.getReader();
// Process stream...
}
return HttpResponse.json({ processed: true });
});Configure handler behavior with options object.
interface RequestHandlerOptions {
/** Remove handler after first match */
once?: boolean;
}// One-time handler
http.get('/api/init', () => {
return HttpResponse.json({ initialized: true });
}, { once: true });// Handler types
interface HttpHandler {
method: HttpMethods | RegExp;
predicate: HttpRequestPredicate<any>;
resolver: HttpResponseResolver<any, any, any>;
options: RequestHandlerOptions;
}
enum HttpMethods {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE',
PATCH = 'PATCH',
HEAD = 'HEAD',
OPTIONS = 'OPTIONS'
}
// Request types
type RequestQuery = Record<string, string | string[]>;
type DefaultBodyType =
| string
| number
| boolean
| null
| undefined
| ArrayBuffer
| Blob
| FormData
| ReadableStream;
type DefaultRequestMultipartBody = Record<string, string | File>;
// Response types
type ResponseResolverReturnType<ResponseBodyType> =
| Response
| HttpResponse<ResponseBodyType>
| Promise<Response>
| Promise<HttpResponse<ResponseBodyType>>;
type AsyncResponseResolverReturnType<ResponseBodyType> = Promise<
ResponseResolverReturnType<ResponseBodyType>
>;