Fetch with JSON

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 dependency 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'
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'
function encodeQuery(query: Record<string, any>) {
return qs.stringify(query)
}
export default async function request(options: FetchOptions) {
options.baseURL = options.baseURL || 'https://example.com/v2'
options.encodeQuery = options.encodeQuery || encodeQuery
options.headers = new Headers(options.headers)
if (!options.headers.has('X-My-Custom-Header')) {
options.headers.set('X-My-Custom-Header', 'header-value')
}
return fetchWithJSON(options)
}
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])
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) => {
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 {
url?: string
json?: any
baseURL?: string
query?: Record<string, any>
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> {
headers: Headers
json: T
error?: Error
ok: boolean
redirected: boolean
status: number
statusText: string
text: string
type: ResponseType
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()
}