light-my-request
repository·main·Indexed 19 days ago
https://github.com/fastify/light-my-requestA fake HTTP injection library for Node.js that allows injecting requests into HTTP servers for testing and debugging without requiring an active socket connection. It supports callback and Promise-based patterns, a fluent method-chaining API, and TypeScript declarations. Key features include the ability to simulate file uploads, pre-configure injectors via bindInject(), and verify injection objects using isInjection().
What's inside light-my-request
- Light my Request is a utility that injects a fake HTTP request/response into a Node.js HTTP server. It is designed for simulating server logic, writing tests, or debugging without requiring an actual socket connection. This means you can run it against an inactive server (one that is not in 'listen' mode).
Use Light my Request with TypeScript
mainThe module includes handwritten TypeScript declarations. You can import the entire namespace or specific members. The following types are exported:
inject: standardinjectmethodbindInject: creates a new inject function with pre-configured default optionsBoundInjectFunction: return type ofbindInjectDispatchFunc: the fake HTTP dispatch functionInjectPayload: union type for valid payload typesisInjection: standardisInjectionmethodInjectOptions: options object forinjectmethodRequest: customrequestobject interface (extends Node.jsstream.Readableby default)Response: customresponseobject interface (extends Node.jshttp.ServerResponse)
// Option 1: Import as namespace import * as LightMyRequest from 'light-my-request' const dispatch: LightMyRequest.DispatchFunc = function (req, res) { // ... } LightMyRequest.inject(dispatch, { method: 'get', url: '/' }, (err, res) => { console.log(res.payload) }) // Option 2: Named imports import { inject, DispatchFunc } from 'light-my-request' const dispatch: DispatchFunc = function (req, res) { // ... } inject(dispatch, { method: 'get', url: '/' }, (err, res) => { console.log(res.payload) })Build requests using the Chain API
mainWhen you call
inject(dispatchFunc, options)without a callback, it returns aChaininstance. This instance provides a fluent interface to configure the request before executing it.Available Chain Methods
HTTP Methods (sets the method and URL):
.get(url).post(url).put(url).patch(url).delete(url).head(url).options(url).trace(url)
Request Configuration:
.body(value).cookies(value).headers(value).payload(value).query(value)
Executing the Request
To trigger the request, call
.end([callback]). If no callback is provided,.end()returns a Promise that resolves to the response.Note: The
Chainobject also inherits fromPromise, so you canawaitthe result of.end()directly.const inject = require('light-my-request') // Fluent chaining pattern inject(handler) .post('/submit') .body({ foo: 'bar' }) .headers({ 'content-type': 'application/json' }) .end((err, res) => { if (err) throw err console.log(res.payload) })Inject a request using the callback pattern
mainThe standard way to use
injectis to pass the server's dispatch function, an options object containing the request details, and a callback function to handle the response.const http = require('node:http') const inject = require('light-my-request') const dispatch = function (req, res) { const reply = 'Hello World' res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': reply.length }) res.end(reply) } const server = http.createServer(dispatch) inject(dispatch, { method: 'get', url: '/' }, (err, res) => { console.log(res.payload) })Use Promises and Async/Await with inject
mainIf you do not provide a callback function to
inject, it returns a Promise, allowing you to use.then()/.catch()orasync/awaitsyntax.// promises inject(dispatch, { method: 'get', url: '/' }) .then(res => console.log(res.payload)) .catch(console.log) // async-await try { const res = await inject(dispatch, { method: 'get', url: '/' }) console.log(res.payload) } catch (err) { console.log(err) }Simulate file uploads and form submissions
mainTo simulate
multipart/form-data(file uploads) orx-www-form-urlencoded(form submits), it is recommended to use theform-auto-contentpackage to generate the necessary request properties.const formAutoContent = require('form-auto-content') const fs = require('node:fs') try { const form = formAutoContent({ myField: 'hello', myFile: fs.createReadStream(`./path/to/file`) }) const res = await inject(dispatch, { method: 'post', url: '/upload', ...form }) console.log(res.payload) } catch (err) { console.log(err) }Create a pre-configured injector with bindInject()
mainUse
bindInject(dispatchFunc, defaults)to create a new injection function that automatically includes common options (like authentication headers) in every request. Thedefaultsare deeply merged with the options provided to the resulting function, but per-request options take precedence.This is ideal for authentication flows where you want to log in once and then use a single function for all subsequent authenticated requests.
const { bindInject } = require('light-my-request') // Create a bound inject function with default authorization header const boundInject = bindInject(dispatch, { headers: { authorization: 'Bearer my-token' } }) // All requests will include the authorization header const res1 = await boundInject({ method: 'get', url: '/protected' }) // You can still add additional headers per request const res2 = await boundInject({ method: 'get', url: '/admin', headers: { 'x-custom': 'value' } })Inject a fake request using inject()
mainThe
inject(dispatchFunc[, options, callback])function simulates an HTTP request by injecting it directly into a listener function (thedispatchFunc). This is useful for testing HTTP servers without actual network overhead.Parameters
dispatchFunc: A listener function with the signaturefunction (req, res). This is the same type of function passed tohttp.createServer.req: A simulated request object (inherits fromStream.Readableby default).res: A simulated response object (inherits from Node'shttp.ServerResponse).
options(Optional): An object to configure the request:url|path: The request URL.method: HTTP method (defaults to'GET').authority: The HTTPHOSTheader value (defaults to'localhost').headers: Object containing request headers. Values can be arrays to simulate multiple headers of the same name.cookies: Key-value pairs to be encoded into thecookieheader.remoteAddress: Client remote address (defaults to'127.0.0.1').payload|body: The request payload (string, Buffer, Stream, or object). Objects are automatically serialized to JSON withContent-type: application/json.query: Object or string containing query parameters.simulate: Object to control event behavior:end: Whether to fire theendevent.split: Whether to split the payload into chunks.error: Whether to emit anerrorevent.close: Whether to emit acloseevent.
signal: AnAbortSignalto abort the request (Node v16+).Request: Custom class for the request object to inherit from.payloadAsStream: Iftrue, response is streamed andres.payload/res.rawPayloadwill be undefined.
callback(Optional): A function with signaturefunction (err, res).
Response Object (
res)If using a callback or awaiting the promise, the response object contains:
raw:{ req, res }(the raw simulated objects).headers: Response headers.statusCode: HTTP status code.statusMessage: HTTP status message.payload: UTF-8 encoded string of the body.body: Alias forpayload.rawPayload: The payload as aBuffer.json(): Function to parse the response payload as JSON.stream(): Function providing aReadablestream of the payload.cookies: Getter that parsesset-cookieheaders into an array of metadata.
const { inject } = require('light-my-request') // Using the shorthand for GET /path const res = await inject(dispatch, '/path') // Using full options const res = await inject(dispatch, { method: 'post', url: '/api/data', payload: { foo: 'bar' } })Use method chaining to build requests
mainYou can build requests using a fluent API. The chain allows you to set the method, URL, and various options before finalizing the request.
Available Methods
- HTTP Methods:
delete,get,head,options,patch,post,put,trace(sets method and URL). - Options:
body,headers,payload,query,cookies. - Finalizer:
end()(returns a Promise if no callback is provided).
Note: You can also use promises without calling
.end()explicitly if you use the method chain directly.// Using .end() with await const chain = inject(dispatch).get('/') try { const res = await chain.end() console.log(res.payload) } catch (err) { // handle error } // Or using promises directly without .end() inject(dispatch) .get('/') .then(res => { console.log(res.payload) }) .catch(err => { // handle error })- HTTP Methods:
Create a reusable injector with bindInject()
mainThe
bindInject()function allows you to create a pre-configured injector by binding adispatchFuncwith a set of default options. This is useful when you want to reuse the same base configuration (like a specific server or default headers) across multiple tests.Returns a function with the signature
(options, callback) => void(or Promise).const inject = require('light-my-request') const baseInject = inject.bindInject(myServerHandler, { server: myServerInstance, headers: { 'x-app-version': '1.0.0' } }) // Now use baseInject with only the specific request details async function test() { const res = await baseInject({ url: '/health' }).get().end() console.log(res.statusCode) }Identify injection objects with isInjection()
mainThe
isInjection()function is a utility to check if an object is a validlight-my-requestRequestorResponseobject (or a custom request object created via theRequestoption).It returns
trueif the object is an instance ofRequest,Response, or has a constructor name of_CustomLMRRequest.const { isInjection } = require('light-my-request') // Assuming 'res' is a response object from an injected request if (isInjection(res)) { console.log('This is a valid LMR response object') }Use inject() to simulate HTTP requests
mainThe
inject()function is the primary entrypoint forlight-my-request. It allows you to simulate HTTP requests against a server handler (dispatch function) without starting a real HTTP server.It supports two usage patterns:
- Callback pattern: Pass a callback function as the third argument to receive the response.
- Promise/Chaining pattern: Omit the callback to receive a
Chainobject, which allows for a fluent API to build the request and returns a Promise.
Request Options
You can pass an options object to configure the request. Supported keys include:
url: The target URL (can be a string).method: The HTTP method (e.g.,'GET','POST').body: The request body.cookies: An object containing cookies.headers: An object containing request headers.payload: The request payload.query: An object containing query string parameters.server: The server instance to bind the request to.Request: A custom Request constructor.autoStart: Boolean. Iffalse, the request won't start automatically viaprocess.nextTick(useful for manual control via.end()).
const inject = require('light-my-request') // Example using the Promise/Chaining API async function test() { const response = await inject(myServerHandler) .get('/path') .query({ id: 123 }) .headers({ 'x-custom-header': 'value' }) .end() console.log(response.payload) }