light-my-request

repository·main·Indexed 19 days ago

https://github.com/fastify/light-my-request

A 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().

Tokens
3.9K
Snippets
11
Records
13
Agent score
15%

What's inside light-my-request

  1. What is Light my Request?

    main
    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).
  2. Use Light my Request with TypeScript

    main

    The module includes handwritten TypeScript declarations. You can import the entire namespace or specific members. The following types are exported:

    • inject: standard inject method
    • bindInject: creates a new inject function with pre-configured default options
    • BoundInjectFunction: return type of bindInject
    • DispatchFunc: the fake HTTP dispatch function
    • InjectPayload: union type for valid payload types
    • isInjection: standard isInjection method
    • InjectOptions: options object for inject method
    • Request: custom request object interface (extends Node.js stream.Readable by default)
    • Response: custom response object interface (extends Node.js http.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)
    })
  3. Build requests using the Chain API

    main

    When you call inject(dispatchFunc, options) without a callback, it returns a Chain instance. 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 Chain object also inherits from Promise, so you can await the 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)
      })
  4. Inject a request using the callback pattern

    main

    The standard way to use inject is 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)
    })
  5. Use Promises and Async/Await with inject

    main

    If you do not provide a callback function to inject, it returns a Promise, allowing you to use .then()/.catch() or async/await syntax.

    // 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)
    }
  6. Simulate file uploads and form submissions

    main

    To simulate multipart/form-data (file uploads) or x-www-form-urlencoded (form submits), it is recommended to use the form-auto-content package 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)
    }
  7. Create a pre-configured injector with bindInject()

    main

    Use bindInject(dispatchFunc, defaults) to create a new injection function that automatically includes common options (like authentication headers) in every request. The defaults are 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' }
    })
  8. Inject a fake request using inject()

    main

    The inject(dispatchFunc[, options, callback]) function simulates an HTTP request by injecting it directly into a listener function (the dispatchFunc). This is useful for testing HTTP servers without actual network overhead.

    Parameters

    • dispatchFunc: A listener function with the signature function (req, res). This is the same type of function passed to http.createServer.
      • req: A simulated request object (inherits from Stream.Readable by default).
      • res: A simulated response object (inherits from Node's http.ServerResponse).
    • options (Optional): An object to configure the request:
      • url | path: The request URL.
      • method: HTTP method (defaults to 'GET').
      • authority: The HTTP HOST header 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 the cookie header.
      • 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 with Content-type: application/json.
      • query: Object or string containing query parameters.
      • simulate: Object to control event behavior:
        • end: Whether to fire the end event.
        • split: Whether to split the payload into chunks.
        • error: Whether to emit an error event.
        • close: Whether to emit a close event.
      • signal: An AbortSignal to abort the request (Node v16+).
      • Request: Custom class for the request object to inherit from.
      • payloadAsStream: If true, response is streamed and res.payload/res.rawPayload will be undefined.
    • callback (Optional): A function with signature function (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 for payload.
    • rawPayload: The payload as a Buffer.
    • json(): Function to parse the response payload as JSON.
    • stream(): Function providing a Readable stream of the payload.
    • cookies: Getter that parses set-cookie headers 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' }
    })
  9. Use method chaining to build requests

    main

    You 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
      })
  10. Create a reusable injector with bindInject()

    main

    The bindInject() function allows you to create a pre-configured injector by binding a dispatchFunc with 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)
    }
  11. Identify injection objects with isInjection()

    main

    The isInjection() function is a utility to check if an object is a valid light-my-request Request or Response object (or a custom request object created via the Request option).

    It returns true if the object is an instance of Request, 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')
    }
  12. Use inject() to simulate HTTP requests

    main

    The inject() function is the primary entrypoint for light-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:

    1. Callback pattern: Pass a callback function as the third argument to receive the response.
    2. Promise/Chaining pattern: Omit the callback to receive a Chain object, 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. If false, the request won't start automatically via process.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)
    }