Skip to content

Commit

Permalink
feat: support automatic json body for post requests (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Feb 19, 2021
1 parent 4ab88c2 commit 97d0987
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 3 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ const { $fetch } = require('ohmyfetch/node')
const { users } = await $fetch('/api/users')
```

## ✔️ JSON Body

`$fetch` automatically stringifies request body (if an object is passed) and adds JSON `Content-Type` headers (for `put`, `patch` and `post` requests).

```js
const { users } = await $fetch('/api/users', { method: 'POST', body: { some: 'json' } })
```

## ✔️ Handling Errors

`$fetch` Automatically throw errors when `response.ok` is `false` with a friendly error message and compact stack (hiding internals).
Expand Down
29 changes: 27 additions & 2 deletions src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ export type FetchRequest = RequestInfo

export interface SearchParams { [key: string]: any }

export interface FetchOptions extends RequestInit {
const payloadMethods = ['patch', 'post', 'put']

export interface FetchOptions extends Omit<RequestInit, 'body'> {
baseURL?: string
body?: RequestInit['body'] | Record<string, any>
params?: SearchParams
response?: boolean
}
Expand All @@ -22,6 +25,24 @@ export interface $Fetch {
raw<T = any>(request: FetchRequest, opts?: FetchOptions): Promise<FetchResponse<T>>
}

export function setHeader (options: FetchOptions, _key: string, value: string) {
const key = _key.toLowerCase()
options.headers = options.headers || {}
if ('set' in options.headers) {
;(options.headers as Headers).set(key, value)
} else if (Array.isArray(options.headers)) {
const existingHeader = options.headers.find(([header]) => header.toLowerCase() === key)
if (existingHeader) {
existingHeader[1] = value
} else {
options.headers.push([key, value])
}
} else {
const existingHeader = Object.keys(options.headers).find(header => header.toLowerCase() === key)
options.headers[existingHeader || key] = value
}
}

export function createFetch ({ fetch }: CreateFetchOptions): $Fetch {
const raw: $Fetch['raw'] = async function (request, opts) {
if (opts && typeof request === 'string') {
Expand All @@ -31,8 +52,12 @@ export function createFetch ({ fetch }: CreateFetchOptions): $Fetch {
if (opts.params) {
request = withQuery(request, opts.params)
}
if (opts.body && opts.body.toString() === '[object Object]' && payloadMethods.includes(opts.method?.toLowerCase() || '')) {
opts.body = JSON.stringify(opts.body)
setHeader(opts, 'content-type', 'application/json')
}
}
const response: FetchResponse<any> = await fetch(request, opts)
const response: FetchResponse<any> = await fetch(request, opts as RequestInit)
const text = await response.text()
response.data = destr(text)
if (!response.ok) {
Expand Down
21 changes: 20 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { listen, Listener } from 'listhen'
import { getQuery } from 'ufo'
import { createApp } from 'h3'
import { createApp, useBody } from 'h3'
import { Headers } from 'node-fetch'
import { $fetch, FetchError } from '../src/node'

describe('ohmyfetch', () => {
Expand All @@ -11,6 +12,7 @@ describe('ohmyfetch', () => {
.use('/ok', () => 'ok')
.use('/params', req => (getQuery(req.url || '')))
.use('/url', req => req.url)
.use('/post', async req => ({ body: await useBody(req), headers: req.headers }))
listener = await listen(app)
})

Expand All @@ -26,6 +28,23 @@ describe('ohmyfetch', () => {
expect(await $fetch('/x?foo=123', { baseURL: listener.getURL('url') })).toBe('/x?foo=123')
})

it('stringifies posts body automatically', async () => {
const { body } = await $fetch(listener.getURL('post'), { method: 'POST', body: { num: 42 } })
expect(body).toEqual({ num: 42 })

const headerFetches = [
[['Content-Type', 'text/html']],
[],
{ 'Content-Type': 'text/html' },
new Headers()
]

for (const sentHeaders of headerFetches) {
const { headers } = await $fetch(listener.getURL('post'), { method: 'POST', body: { num: 42 }, headers: sentHeaders as any })
expect(headers).toEqual(expect.objectContaining({ 'content-type': 'application/json' }))
}
})

it('404', async () => {
const err: FetchError = await $fetch(listener.getURL('404')).catch(err => err)
expect(err.stack).toMatch('404 Not Found')
Expand Down

0 comments on commit 97d0987

Please sign in to comment.