Skip to content

Commit 7bb2e3b

Browse files
feat: adds X-HTTP-Method-Override header (#6487)
Fixes: #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.
1 parent 78db50a commit 7bb2e3b

File tree

5 files changed

+121
-9
lines changed

5 files changed

+121
-9
lines changed

docs/rest-api/overview.mdx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,3 +618,45 @@ export const Orders: CollectionConfig = {
618618
**req** will have the **payload** object and can be used inside your endpoint handlers for making
619619
calls like req.payload.find() that will make use of access control and hooks.
620620
</Banner>
621+
622+
## Method Override for GET Requests
623+
624+
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.
625+
626+
### How to Use
627+
628+
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`.
629+
630+
### Example
631+
632+
Here is an example of how to use the method override to perform a GET request:
633+
634+
#### Using Method Override (POST)
635+
636+
```ts
637+
const res = await fetch(`${api}/${collectionSlug}`, {
638+
method: 'POST',
639+
credentials: 'include',
640+
headers: {
641+
'Accept-Language': i18n.language,
642+
'Content-Type': 'application/x-www-form-urlencoded',
643+
'X-HTTP-Method-Override': 'GET',
644+
},
645+
body: qs.stringify({
646+
depth: 1,
647+
locale: 'en',
648+
}),
649+
})
650+
```
651+
652+
#### Equivalent Regular GET Request
653+
654+
```ts
655+
const res = await fetch(`${api}/${collectionSlug}?depth=1&locale=en`, {
656+
method: 'GET',
657+
credentials: 'include',
658+
headers: {
659+
'Accept-Language': i18n.language,
660+
},
661+
})
662+
```

packages/next/src/routes/rest/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ export const POST =
407407
let res: Response
408408
let collection: Collection
409409

410+
const overrideHttpMethod = request.headers.get('X-HTTP-Method-Override')
411+
if (overrideHttpMethod === 'GET') {
412+
return await GET(config)(request, { params: { slug } })
413+
}
414+
410415
try {
411416
req = await createPayloadRequest({
412417
config,

packages/next/src/utilities/createPayloadRequest.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ export const createPayloadRequest = async ({
5858
fallbackLocale = locales.fallbackLocale
5959
}
6060

61+
const overrideHttpMethod = request.headers.get('X-HTTP-Method-Override')
62+
const queryToParse = overrideHttpMethod === 'GET' ? await request.text() : urlProperties.search
63+
64+
const query = queryToParse
65+
? qs.parse(queryToParse, {
66+
arrayLimit: 1000,
67+
depth: 10,
68+
ignoreQueryPrefix: true,
69+
})
70+
: {}
71+
6172
const customRequest: CustomPayloadRequestProperties = {
6273
context: {},
6374
fallbackLocale,
@@ -74,13 +85,7 @@ export const createPayloadRequest = async ({
7485
payloadUploadSizes: {},
7586
port: urlProperties.port,
7687
protocol: urlProperties.protocol,
77-
query: urlProperties.search
78-
? qs.parse(urlProperties.search, {
79-
arrayLimit: 1000,
80-
depth: 10,
81-
ignoreQueryPrefix: true,
82-
})
83-
: {},
88+
query,
8489
routeParams: params || {},
8590
search: urlProperties.search,
8691
searchParams: urlProperties.searchParams,

packages/ui/src/fields/Relationship/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,15 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
201201
query.where.and.push(relationFilterOption)
202202
}
203203

204-
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(query)}`, {
204+
const response = await fetch(`${serverURL}${api}/${relation}`, {
205+
body: qs.stringify(query),
205206
credentials: 'include',
206207
headers: {
207208
'Accept-Language': i18n.language,
209+
'Content-Type': 'application/x-www-form-urlencoded',
210+
'X-HTTP-Method-Override': 'GET',
208211
},
212+
method: 'POST',
209213
})
210214

211215
if (response.ok) {
@@ -326,11 +330,15 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
326330
}
327331

328332
if (!errorLoading) {
329-
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(query)}`, {
333+
const response = await fetch(`${serverURL}${api}/${relation}`, {
334+
body: qs.stringify(query),
330335
credentials: 'include',
331336
headers: {
332337
'Accept-Language': i18n.language,
338+
'Content-Type': 'application/x-www-form-urlencoded',
339+
'X-HTTP-Method-Override': 'GET',
333340
},
341+
method: 'POST',
334342
})
335343

336344
const collection = collections.find((coll) => coll.slug === relation)

test/fields-relationship/e2e.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,58 @@ describe('fields - relationship', () => {
569569
).toHaveCount(15)
570570
})
571571
})
572+
573+
describe('field relationship with many items', () => {
574+
beforeEach(async () => {
575+
const relations: string[] = []
576+
const batchSize = 10
577+
const totalRelations = 300
578+
const totalBatches = Math.ceil(totalRelations / batchSize)
579+
for (let i = 0; i < totalBatches; i++) {
580+
const batchPromises: Promise<RelationOne>[] = []
581+
const start = i * batchSize
582+
const end = Math.min(start + batchSize, totalRelations)
583+
584+
for (let j = start; j < end; j++) {
585+
batchPromises.push(
586+
payload.create({
587+
collection: relationOneSlug,
588+
data: {
589+
name: 'relation',
590+
},
591+
}),
592+
)
593+
}
594+
595+
const batchRelations = await Promise.all(batchPromises)
596+
relations.push(...batchRelations.map((doc) => doc.id))
597+
}
598+
599+
await payload.update({
600+
id: docWithExistingRelations.id,
601+
collection: slug,
602+
data: {
603+
relationshipHasMany: relations,
604+
},
605+
})
606+
})
607+
608+
test('should update with new relationship', async () => {
609+
await page.goto(url.edit(docWithExistingRelations.id))
610+
611+
const field = page.locator('#field-relationshipHasMany')
612+
const dropdownIndicator = field.locator('.dropdown-indicator')
613+
await dropdownIndicator.click({ delay: 100 })
614+
615+
const options = page.locator('.rs__option')
616+
await expect(options).toHaveCount(2)
617+
618+
await options.nth(0).click()
619+
await expect(field).toContainText(relationOneDoc.id)
620+
621+
await saveDocAndAssert(page)
622+
})
623+
})
572624
})
573625

574626
async function clearAllDocs(): Promise<void> {

0 commit comments

Comments
 (0)