npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fetch-with-json

v1.0.0

Published

Some tweaks to the Fetch API to make it easier to communicate with the server using JSON.

Downloads

21

Readme

Fetch with JSON

npm version install size npm downloads

Some tweaks to the Fetch API to make it easier to communicate with the server using JSON.

Using this library, you don't need to manually set the request header "Content-Type" to "application/json" every time you send JSON to the server.

This library does the following for you:

  • Stringify the request data (the json field in the options) with JSON.stringify before sending it to the server as request body. See the example here.
  • Set the request header Content-Type to application/json and Accept to application/json, */* if they are not set.
  • Always try to parse response body as JSON no matter what the response header Content-Type is and assign the parsed result to response.json. If error has be thrown during parsing, response.json will be set to the text of the original response body, and the error will be assigned to response.error.

This library is a zero dependencies module and will always be. This library only extends the fetch options, not overrides it. So you can use the functionality provided by the original Fetch API. For example, you can use this library to upload files just like you would with Fetch API. And you can get the original Response if you want.

Examples:

Installation

npm i fetch-with-json

Examples

1. Posting JSON data

import request from 'fetch-with-json'

request({
  method: 'POST',
  url: '/posts',
  json: {
    title: 'Fetch API',
    content: 'The Fetch API provides an interface for fetching resources.'
  }
}).then((res) => {
  console.log(`post created, id = ${res.json.id}`)
})

The type of res is FetchResponse and the json field of res is the parsed JSON object.

2. Using query

import request from 'fetch-with-json'

// GET /posts?page=1&size=10&category=typescript
request({
  url: '/posts',
  query: {
    page: 1,
    size: 10,
    category: 'typescript'
  }
})

The query object will be encoded to query string with a default encodeQuery function. If the default encodeQuery function does not meet your needs, you can override it by setting the encodeQuery option. This is very useful when you are creating your own request method based on this library. See the coming example for more details.

3. Creating your own request with default options

import qs from 'qs'
import fetchWithJSON, { FetchOptions } from 'fetch-with-json'

// Encode query with `qs` module
function encodeQuery(query: Record<string, any>) {
  return qs.stringify(query)
}

export default async function request(options: FetchOptions) {
  // Set baseURL if it is not set
  options.baseURL = options.baseURL || 'https://example.com/v2'

  // Set encodeQuery if it is not set to override the default one
  options.encodeQuery = options.encodeQuery || encodeQuery

  // Extend the original headers
  options.headers = new Headers(options.headers)

  // Set X-My-Custom-Header if it is not set
  if (!options.headers.has('X-My-Custom-Header')) {
    options.headers.set('X-My-Custom-Header', 'header-value')
  }

  return fetchWithJSON(options)
}

// GET https://example.com/v2/posts
request({ url: '/posts' })

4. Uploading a file

import request from 'fetch-with-json'

const formData = new FormData()
const fileField = document.querySelector('input[type="file"]')

formData.append('avatar', fileField.files[0])

// You can add other fields, e.g:
// formData.append("username", "abc123");

// The Fetch API will set request header "Content-Type" to
// "multipart/form-data" automatically if the type of body
// is FormData.
request({
  method: 'POST',
  url: '/users/profile',
  body: formData
}).then((res) => {
  console.log(res)
})

Please note that we are using the body field to upload the FormData instead of using the json field. The body field is declared in the parameters of the Fetch API. When body is set (not null or undefined), the json field will be ignored.

5. Getting the original Response

If you want to get the original Response returned by the Fetch API, please set the second parameter to true, below is an example.

import request from 'fetch-with-json'

request({ url: '/posts/1' }, true).then((res) => {
  // The type of res is Response
  console.log(res)
})

Typescript Declarations

FetchMethod

The request method has two declarations. The primary one is:

function request<T = any>(options: FetchOptions): Promise<FetchResponse<T>>

The above one will always convert the Response to FetchResponse. If you want to get the original Response, you can use the second declaration:

function request(options: FetchOptions, rawResponse: true): Promise<Response>

Put them together, below is the declaration of FetchMethod:

interface FetchMethod {
  <T = any>(options: FetchOptions): Promise<FetchResponse<T>>
  <T = any>(options: FetchOptions, rawResponse: true): Promise<Response>
}

Note: We add generic type to the second one just to keep it consistent with the first one (the generic type for the second one actually has no effects).

FetchOptions

The declaration of FetchOptions is:

interface FetchOptions extends RequestInit {
  /**
   * The request url.
   */
  url?: string

  /**
   * The data to send to the server. The data will be stringified using
   * `JSON.stringify` before being sent to the server and the `Content-Type`
   * request header will be set to `application/json` if not set.
   *
   * If the `body` field is set (not `null` or `undefined`), the `json`
   * field will be ignored and we will not set the `Content-Type` header.
   */
  json?: any

  /**
   * The base URL to prepend to `url` if `url` is a relative url.
   */
  baseURL?: string

  /**
   * The value to be encoded to query string to append to the url.
   */
  query?: Record<string, any>

  /**
   * A function to encode the `query` value.
   * A query string must be returned without a leading question mark.
   * If this function is not set, the default one will be used.
   */
  encodeQuery?: (query: Record<string, any>) => string
}

For the many other options (the options declared in the RequestInit type) provided by the original Fetch API, please click here to read the documentation of the fetch parameters on the MDN website.

FetchResponse

The declaration of FetchResponse is:

interface FetchResponse<T = any> {
  /**
   * The `Headers` object associated with the response.
   */
  headers: Headers

  /**
   * The response data. Parsed from the response body text with `JSON.parse`.
   * If parsing fails, this field will be set to the response body text and
   * the `error` field of the response will be set to the error thrown
   * during the parsing.
   */
  json: T

  /**
   * The error thrown while parsing the response body text with `JSON.parse`.
   * If no error has been thrown, the value of this field is `undefined`.
   */
  error?: Error

  /**
   * A boolean indicating whether the response was successful (status in the
   * range 200 – 299) or not.
   */
  ok: boolean

  /**
   * Indicates whether or not the response is the result of a redirect
   * (that is, its URL list has more than one entry).
   */
  redirected: boolean

  /**
   * The status code of the response.
   */
  status: number

  /**
   * The status message corresponding to the status code. (e.g., OK for 200).
   */
  statusText: string

  /**
   * The original response body text.
   */
  text: string

  /**
   * The type of the response (e.g., basic, cors).
   */
  type: ResponseType

  /**
   * The URL of the response.
   */
  url: string
}

The default encodeQuery function

Here is the implementation of the default encodeQuery function for your reference.

function defaultEncodeQuery(query: Record<string, any>) {
  const hasOwn = Object.prototype.hasOwnProperty
  const params = new URLSearchParams()

  for (const key in query) {
    if (hasOwn.call(query, key)) {
      const val = query[key]
      if (val != null) {
        if (Array.isArray(val)) {
          val.forEach((elem) => params.append(key, '' + elem))
        } else {
          params.append(key, '' + val)
        }
      }
    }
  }

  return params.toString()
}