feat: adds X-HTTP-Method-Override header (#6487)

Fixes: https://github.com/payloadcms/payload/issues/6486

Adds `X-HTTP-Method-Override` header to allow for sending query params in the body of a POST request. This is useful when the query param string hits the upper limit.
This commit is contained in:
Anders Semb Hermansen
2024-06-13 21:27:39 +02:00
committed by GitHub
parent 78db50a497
commit 7bb2e3be76
5 changed files with 121 additions and 9 deletions

View File

@@ -618,3 +618,45 @@ export const Orders: CollectionConfig = {
**req** will have the **payload** object and can be used inside your endpoint handlers for making
calls like req.payload.find() that will make use of access control and hooks.
</Banner>
## Method Override for GET Requests
Payload supports a method override feature that allows you to send GET requests using the HTTP POST method. This can be particularly useful in scenarios when the query string in a regular GET request is too long.
### How to Use
To use this feature, include the `X-HTTP-Method-Override` header set to `GET` in your POST request. The parameters should be sent in the body of the request with the `Content-Type` set to `application/x-www-form-urlencoded`.
### Example
Here is an example of how to use the method override to perform a GET request:
#### Using Method Override (POST)
```ts
const res = await fetch(`${api}/${collectionSlug}`, {
method: 'POST',
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/x-www-form-urlencoded',
'X-HTTP-Method-Override': 'GET',
},
body: qs.stringify({
depth: 1,
locale: 'en',
}),
})
```
#### Equivalent Regular GET Request
```ts
const res = await fetch(`${api}/${collectionSlug}?depth=1&locale=en`, {
method: 'GET',
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
},
})
```

View File

@@ -407,6 +407,11 @@ export const POST =
let res: Response
let collection: Collection
const overrideHttpMethod = request.headers.get('X-HTTP-Method-Override')
if (overrideHttpMethod === 'GET') {
return await GET(config)(request, { params: { slug } })
}
try {
req = await createPayloadRequest({
config,

View File

@@ -58,6 +58,17 @@ export const createPayloadRequest = async ({
fallbackLocale = locales.fallbackLocale
}
const overrideHttpMethod = request.headers.get('X-HTTP-Method-Override')
const queryToParse = overrideHttpMethod === 'GET' ? await request.text() : urlProperties.search
const query = queryToParse
? qs.parse(queryToParse, {
arrayLimit: 1000,
depth: 10,
ignoreQueryPrefix: true,
})
: {}
const customRequest: CustomPayloadRequestProperties = {
context: {},
fallbackLocale,
@@ -74,13 +85,7 @@ export const createPayloadRequest = async ({
payloadUploadSizes: {},
port: urlProperties.port,
protocol: urlProperties.protocol,
query: urlProperties.search
? qs.parse(urlProperties.search, {
arrayLimit: 1000,
depth: 10,
ignoreQueryPrefix: true,
})
: {},
query,
routeParams: params || {},
search: urlProperties.search,
searchParams: urlProperties.searchParams,

View File

@@ -201,11 +201,15 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
query.where.and.push(relationFilterOption)
}
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(query)}`, {
const response = await fetch(`${serverURL}${api}/${relation}`, {
body: qs.stringify(query),
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/x-www-form-urlencoded',
'X-HTTP-Method-Override': 'GET',
},
method: 'POST',
})
if (response.ok) {
@@ -326,11 +330,15 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
}
if (!errorLoading) {
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(query)}`, {
const response = await fetch(`${serverURL}${api}/${relation}`, {
body: qs.stringify(query),
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/x-www-form-urlencoded',
'X-HTTP-Method-Override': 'GET',
},
method: 'POST',
})
const collection = collections.find((coll) => coll.slug === relation)

View File

@@ -569,6 +569,58 @@ describe('fields - relationship', () => {
).toHaveCount(15)
})
})
describe('field relationship with many items', () => {
beforeEach(async () => {
const relations: string[] = []
const batchSize = 10
const totalRelations = 300
const totalBatches = Math.ceil(totalRelations / batchSize)
for (let i = 0; i < totalBatches; i++) {
const batchPromises: Promise<RelationOne>[] = []
const start = i * batchSize
const end = Math.min(start + batchSize, totalRelations)
for (let j = start; j < end; j++) {
batchPromises.push(
payload.create({
collection: relationOneSlug,
data: {
name: 'relation',
},
}),
)
}
const batchRelations = await Promise.all(batchPromises)
relations.push(...batchRelations.map((doc) => doc.id))
}
await payload.update({
id: docWithExistingRelations.id,
collection: slug,
data: {
relationshipHasMany: relations,
},
})
})
test('should update with new relationship', async () => {
await page.goto(url.edit(docWithExistingRelations.id))
const field = page.locator('#field-relationshipHasMany')
const dropdownIndicator = field.locator('.dropdown-indicator')
await dropdownIndicator.click({ delay: 100 })
const options = page.locator('.rs__option')
await expect(options).toHaveCount(2)
await options.nth(0).click()
await expect(field).toContainText(relationOneDoc.id)
await saveDocAndAssert(page)
})
})
})
async function clearAllDocs(): Promise<void> {