mswjs/interceptors: network interception at the TCP handle in Node.js
Low-level network interception library for Node.js.
At a glance
- What is it?
- This is a low-level interception library for Node.js that hooks the socket layer rather than replacing http.request, and the README positions it as building material for mocking libraries rather than a mocking library itself.
- Who is it for?
- Adopt @mswjs/interceptors if you are writing a mocking or debugging tool and need requests to travel through real Node.js network code. Do not reach for it to stub an endpoint in an application test, where a higher-level mocking library is less work.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Interception happens at the TCP handle, below http.request()
The README frames Node.js networking as a stack that a mocking layer can enter at different depths, and it is explicit that patching http.request() short-circuits everything below it:
1. Third-party request client (axios/got/etc);
---- node:http / node:https ----
2. http.request() (node:http/node:https);
3. new http.ClientRequest();
---- node:net / node:tls ----
4. net.connect() / tls.connect();
5. new net.Socket();
6. socket.connect();
---- native bindings ----
7. TCPWrap / TLSWrap;
---- C++ network code ----
8. [TOO_COMPLEX_TO_FATHOM];Interceptors works at step 7, the native TCPWrap and TLSWrap handles, which the documentation calls the lowest possible level to spy on outgoing traffic without recompiling Node.js. The stated consequence is that mocking a request to a host that does not exist still establishes a connection, because the request path above the handle is left intact. That is the whole design argument: an interception that returns a canned response at step 2 changes how the system behaves, while one at step 7 mostly does not.
SocketInterceptor holds every connection pending until you claim it
SocketInterceptor is the base layer and the lowest-level entry point in the library. The README states it intercepts every outgoing TCP and TLS connection in Node.js at the net.Socket level, no matter which module or third-party package created it. The API shape matters more than the reach: a connection listener receives the socket, the connection options and a controller, and the connection stays in a pending state until the listener calls controller.claim() or controller.passthrough().
import { SocketInterceptor } from '@mswjs/interceptors/net'
const interceptor = new SocketInterceptor()
interceptor.on('connection', ({ socket, connectionOptions, controller }) => {
if (connectionOptions.host === 'example.com') {
controller.claim()
socket.on('data', (chunk) => {
socket.write(anotherChunk)
})
}
})
interceptor.apply()One detail catches people out. The exposed socket is mirrored, so the connection listener behaves like a server-side handler: it emits data when the client writes to it, and writing to it produces data events on the intercepted socket. There is also no default timeout. A listener that claims nothing but also passes nothing through leaves connections hanging by design.
Higher level interceptors parse packets instead of replacing requests
Above the socket layer sit the protocol interceptors. HttpRequestInterceptor covers HTTP requests in Node.js regardless of which client issued them, and the README explains that because the underlying interception is at the socket level, this includes the http and https modules, the global fetch, and direct Undici usage. The description of the layering is that socket level interception is wrapped in higher-level interceptors such as HttpRequestInterceptor, which pipe outgoing and incoming socket packets through parsers. The parser, not the mock, is the mechanism.
The exported entry points cover the request-issuing modules a test suite is likely to touch: SocketInterceptor, HttpRequestInterceptor, ClientRequestInterceptor for http.ClientRequest and http.get, XMLHttpRequestInterceptor, FetchInterceptor for the global fetch, and WebSocketInterceptor for the global WebSocket constructor. Several of them can be combined with BatchInterceptor, which the README lists as the way to combine multiple interceptors at once.
AsyncLocalStorage tags the caller without touching the traffic
The fourth layer exists because a socket connection has no idea which request client caused it. The README states that a socket connection is unaware of any protocols, let alone the request clients that triggered the connection. The solution described is a set of request client interceptors that leverage AsyncLocalStorage to annotate request initiators without intercepting any traffic themselves.
That is a subtle design decision with real consequences. Metadata about the origin of a request rides along with the async context instead of being pushed down through the call stack of the network layer, which is what lets a mock decide to respond differently for the same URL depending on which component asked for it. It also means the context has to be established before the connection is opened, since AsyncLocalStorage propagates through the async execution context rather than through the socket itself.
Installing the package and switching on the debug logs
The package is published to npm as @mswjs/interceptors and the project is written in TypeScript. Installation is a single command:
npm i @mswjs/interceptorsThe README covers debugging through the debug namespaces. Default logs cover interceptor lifecycle, requests and request resolution:
DEBUG='interceptors:*' node app.jsVerbose output adds socket packets, event forwarding and other internals:
DEBUG='interceptors:*' DEBUG_LEVEL=verbose node app.jsLogging can be narrowed to a single interceptor using its lowercase kebab-case name, and the names given as examples are interceptors:fetch, interceptors:xhr, interceptors:client-request and interceptors:websocket. In a browser the same value goes into localStorage.debug, with localStorage.debugLevel set to verbose for the verbose level. The README notes that each namespace has a stable color, which is a small but practical detail when several interceptors log at once.
The intended customer is someone writing a mocking library
The README states plainly that Interceptors is not an API mocking library, that mocking the network is just a subset of what it can do, and that if you are uncertain whether you need it, you probably do not. Its stated purpose is to help other developers build their own higher-level API mocking libraries, naming Nock and Mock Service Worker as the kind of projects that benefit, with the goal of unifying the network interception algorithm.
That framing is the actual difference in approach between the two layers. A mocking library offers request matching, handler definitions and a response DSL. Interceptors offers no handler registry at all; you get socket events and parsers, and you assemble the policy yourself. For an application team writing ordinary tests, the difference means more code on your side. For a tool author who wants a mock that still runs through Node.js request handling, that extra layer is the reason to choose this package.
The export map limits which entry points exist in the browser
The package manifest is where the platform constraints show. The root export points to a browser build under the browser condition and to the Node build otherwise. The net, http and ClientRequest entries carry a browser condition of null, so those three are unavailable in a browser bundle. The XMLHttpRequest, fetch and WebSocket entries each ship separate node and web files, and there are dedicated presets for node and for browser. A RemoteHttpInterceptor entry points at a single Node-only file.
That split matters if you were hoping for one interception implementation across runtimes. Node-only socket work cannot be mirrored into a browser, so a browser test setup will use the XHR and fetch interceptors only. The repository layout is otherwise conventional for a TypeScript library, with a src directory, a test directory, a pnpm workspace file, vitest configuration files and a tsdown build configuration, so the build output at lib/node and lib/browser is produced from that source tree.
Maintenance is current and the license is permissive
The repository is not archived, and the last push was on 2026-10-17, with releases v0.45.9 on 2026-10-17, v0.45.8 on 2026-10-13 and v0.45.7 on 2026-10-04. The package version in the manifest matches the newest of those tags at 0.45.9. The license is MIT.
The version number is worth reading as a signal rather than a promise. A zero major with patch-level releases moving every few days suggests an API that is still settling, which for a library whose whole purpose is to be embedded inside other people's tools is a reasonable tradeoff but also a reason to pin an exact version rather than floating on the newest tag. On licensing, the MIT terms are short and permissive; the point to check is attribution and notice retention in anything you ship that embeds this code, and that is a question for your own legal review rather than something this project settles.
Editorial conclusion
Adopt @mswjs/interceptors if you are writing a mocking or debugging tool and need requests to travel through real Node.js network code. Do not reach for it to stub an endpoint in an application test, where a higher-level mocking library is less work. Before committing, check that the entry point you need is exported in the platform you target, since the net, http and ClientRequest entries have a null browser condition, and confirm the version you pin matches the release you read.
Frequently asked questions
Is @mswjs/interceptors a mocking library on its own?
No. The README states that Interceptors is not an API mocking library but a low-level network interception library, and that mocking the network is only a subset of what it can do. It is meant as the substrate for libraries such as Nock or Mock Service Worker.
Which request types can @mswjs/interceptors intercept in Node.js?
The README lists raw TCP and TLS socket connections, HTTP requests regardless of the client, fetch requests including custom fetch implementations such as undici, and WebSocket connections through the global WebSocket constructor. The exported interceptors include SocketInterceptor, HttpRequestInterceptor, ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor and WebSocketInterceptor.
How do I get debug logs from @mswjs/interceptors?
Set the DEBUG environment variable to the interceptors namespace, for example DEBUG='interceptors:*' node app.js, and add DEBUG_LEVEL=verbose for socket packets and event forwarding. Scopes such as interceptors:fetch or interceptors:websocket narrow the output to one interceptor.
Can @mswjs/interceptors be used in a browser build?
Partly. The package manifest sets a null browser condition for the net, http and ClientRequest entries, so those are Node-only. The XMLHttpRequest, fetch and WebSocket entries each provide separate node and web files, and there are node and browser presets.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mswjs-interceptors)Was this useful?