Skip to content

Commit bd0161b

Browse files
fix: content-type application/json behavior for client (#387)
* fix content type header * fix tests * fix tests * more test fixes * Update libs/ts-rest/core/src/lib/client.ts Co-authored-by: Youssef Gaber <1728215+Gabrola@users.noreply.github.com> * changeset * fix tests --------- Co-authored-by: Youssef Gaber <1728215+Gabrola@users.noreply.github.com>
1 parent ffb2d41 commit bd0161b

7 files changed

Lines changed: 119 additions & 70 deletions

File tree

.changeset/popular-ants-relax.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@ts-rest/core': patch
3+
'@ts-rest/vue-query': patch
4+
---
5+
6+
fix: content-type application/json will not be automatically included in client request if the route is GET or body is undefined/null

apps/example-next/tests/react-query.spec.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ describe('react-query', () => {
184184
path: 'https://api.com/health',
185185
body: undefined,
186186
headers: {
187-
'content-type': 'application/json',
188187
'x-test': 'test',
189188
},
190189
route: router.health,
@@ -225,7 +224,6 @@ describe('react-query', () => {
225224
path: 'https://api.com/posts/1',
226225
body: undefined,
227226
headers: {
228-
'content-type': 'application/json',
229227
'x-test': 'test',
230228
},
231229
route: router.posts.getPost,
@@ -298,7 +296,6 @@ describe('react-query', () => {
298296
path: 'https://api.com/posts/1',
299297
body: undefined,
300298
headers: {
301-
'content-type': 'application/json',
302299
'x-test': 'test',
303300
},
304301
route: router.posts.getPost,
@@ -325,7 +322,6 @@ describe('react-query', () => {
325322
path: 'https://api.com/health',
326323
body: undefined,
327324
headers: {
328-
'content-type': 'application/json',
329325
'x-test': 'test',
330326
},
331327
route: router.health,
@@ -358,7 +354,6 @@ describe('react-query', () => {
358354
path: 'https://api.com/health',
359355
body: undefined,
360356
headers: {
361-
'content-type': 'application/json',
362357
'x-test': 'test',
363358
},
364359
route: router.health,
@@ -468,7 +463,6 @@ describe('react-query', () => {
468463
path: 'https://api.com/posts/1',
469464
body: undefined,
470465
headers: {
471-
'content-type': 'application/json',
472466
'x-test': 'test',
473467
},
474468
route: router.posts.getPost,
@@ -480,7 +474,6 @@ describe('react-query', () => {
480474
path: 'https://api.com/posts/2',
481475
body: undefined,
482476
headers: {
483-
'content-type': 'application/json',
484477
'x-test': 'test',
485478
},
486479
route: router.posts.getPost,
@@ -541,7 +534,6 @@ describe('react-query', () => {
541534
path: 'https://api.com/posts/1',
542535
body: undefined,
543536
headers: {
544-
'content-type': 'application/json',
545537
'x-test': 'test',
546538
},
547539
route: router.posts.getPost,
@@ -553,7 +545,6 @@ describe('react-query', () => {
553545
path: 'https://api.com/posts/2',
554546
body: undefined,
555547
headers: {
556-
'content-type': 'application/json',
557548
'x-test': 'test',
558549
},
559550
route: router.posts.getPost,
@@ -618,7 +609,6 @@ describe('react-query', () => {
618609
path: 'https://api.com/posts/1',
619610
body: undefined,
620611
headers: {
621-
'content-type': 'application/json',
622612
'x-test': 'test',
623613
},
624614
route: router.posts.getPost,
@@ -630,7 +620,6 @@ describe('react-query', () => {
630620
path: 'https://api.com/posts/2',
631621
body: undefined,
632622
headers: {
633-
'content-type': 'application/json',
634623
'x-test': 'test',
635624
},
636625
route: router.posts.getPost,
@@ -673,7 +662,6 @@ describe('react-query', () => {
673662
path: 'https://api.com/posts/1',
674663
body: undefined,
675664
headers: {
676-
'content-type': 'application/json',
677665
'x-test': 'test',
678666
},
679667
route: router.posts.getPost,
@@ -703,7 +691,6 @@ describe('react-query', () => {
703691
path: 'https://api.com/posts/1',
704692
body: undefined,
705693
headers: {
706-
'content-type': 'application/json',
707694
'x-test': 'test',
708695
},
709696
route: router.posts.getPost,
@@ -739,7 +726,6 @@ describe('react-query', () => {
739726
path: 'https://api.com/posts/1',
740727
body: undefined,
741728
headers: {
742-
'content-type': 'application/json',
743729
'x-test': 'test',
744730
},
745731
route: router.posts.getPost,
@@ -769,7 +755,6 @@ describe('react-query', () => {
769755
path: 'https://api.com/posts/1',
770756
body: undefined,
771757
headers: {
772-
'content-type': 'application/json',
773758
'x-test': 'test',
774759
},
775760
route: router.posts.getPost,
@@ -816,7 +801,6 @@ describe('react-query', () => {
816801
path: 'https://api.com/posts/1',
817802
body: undefined,
818803
headers: {
819-
'content-type': 'application/json',
820804
'x-test': 'test',
821805
},
822806
route: router.posts.getPost,

libs/ts-rest/core/src/lib/client.spec.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,6 @@ describe('client', () => {
225225
fetchMock.getOnce(
226226
{
227227
url: 'https://api.com/posts',
228-
headers: {
229-
'Content-Type': 'application/json',
230-
},
231228
},
232229
{ body: value, status: 200 }
233230
);
@@ -245,9 +242,6 @@ describe('client', () => {
245242
fetchMock.getOnce(
246243
{
247244
url: 'https://api.com/posts',
248-
headers: {
249-
'Content-Type': 'application/json',
250-
},
251245
},
252246
{ body: value, status: 200 }
253247
);
@@ -265,9 +259,6 @@ describe('client', () => {
265259
fetchMock.getOnce(
266260
{
267261
url: 'https://api.com/posts',
268-
headers: {
269-
'Content-Type': 'application/json',
270-
},
271262
},
272263
{ body: value, status: 200 }
273264
);
@@ -285,9 +276,6 @@ describe('client', () => {
285276
fetchMock.getOnce(
286277
{
287278
url: 'https://api.com/posts?take=10',
288-
headers: {
289-
'Content-Type': 'application/json',
290-
},
291279
},
292280
{ body: value, status: 200 }
293281
);
@@ -326,9 +314,6 @@ describe('client', () => {
326314
url: `https://api.com/posts?take=10&order=asc&published=true&filter=${encodeURIComponent(
327315
'{"title":"test"}'
328316
)}`,
329-
headers: {
330-
'Content-Type': 'application/json',
331-
},
332317
},
333318
{ body: value, status: 200 }
334319
);
@@ -354,9 +339,6 @@ describe('client', () => {
354339
fetchMock.getOnce(
355340
{
356341
url: 'https://api.com/posts?take=10',
357-
headers: {
358-
'Content-Type': 'application/json',
359-
},
360342
},
361343
{ body: value, status: 200 }
362344
);
@@ -368,17 +350,13 @@ describe('client', () => {
368350
expect(result.body).toStrictEqual(value);
369351
expect(result.status).toBe(200);
370352
expect(result.headers.get('Content-Length')).toBe('15');
371-
expect(result.headers.get('Content-Type')).toBe('application/json');
372353
});
373354

374355
it('w/ sub path', async () => {
375356
const value = { key: 'value' };
376357
fetchMock.getOnce(
377358
{
378359
url: 'https://api.com/posts/1',
379-
headers: {
380-
'Content-Type': 'application/json',
381-
},
382360
},
383361
{ body: value, status: 200 }
384362
);
@@ -388,16 +366,12 @@ describe('client', () => {
388366
expect(result.body).toStrictEqual(value);
389367
expect(result.status).toBe(200);
390368
expect(result.headers.get('Content-Length')).toBe('15');
391-
expect(result.headers.get('Content-Type')).toBe('application/json');
392369
});
393370

394371
it('w/ a non json response (string)', async () => {
395372
fetchMock.getOnce(
396373
{
397374
url: 'https://api.com/posts',
398-
headers: {
399-
'Content-Type': 'application/json',
400-
},
401375
},
402376
{
403377
headers: {
@@ -500,9 +474,6 @@ describe('client', () => {
500474
fetchMock.patchOnce(
501475
{
502476
url: 'https://api.com/posts/1',
503-
headers: {
504-
'Content-Type': 'application/json',
505-
},
506477
},
507478
{ body: value, status: 200 }
508479
);
@@ -524,9 +495,6 @@ describe('client', () => {
524495
fetchMock.deleteOnce(
525496
{
526497
url: 'https://api.com/posts/1',
527-
headers: {
528-
'Content-Type': 'application/json',
529-
},
530498
},
531499
{ body: value, status: 200 }
532500
);
@@ -713,7 +681,6 @@ describe('custom api', () => {
713681
headers: {
714682
'base-header': 'foo',
715683
'x-test': 'test',
716-
'content-type': 'application/json',
717684
},
718685
})
719686
);

libs/ts-rest/core/src/lib/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,23 @@ export const fetchApi = ({
221221
});
222222
}
223223

224+
const includeContentTypeHeader =
225+
route.method !== 'GET' && body !== null && body !== undefined;
226+
224227
return apiFetcher({
225228
route,
226229
path,
227230
method: route.method,
228231
credentials: clientArgs.credentials,
229232
headers: {
230-
'content-type': 'application/json',
233+
...(includeContentTypeHeader && { 'content-type': 'application/json' }),
231234
...combinedHeaders,
232235
},
233236
body:
234237
body !== null && body !== undefined ? JSON.stringify(body) : undefined,
235238
rawBody: body,
236239
rawQuery: query,
237-
contentType: route.method !== 'GET' ? 'application/json' : undefined,
240+
contentType: includeContentTypeHeader ? 'application/json' : undefined,
238241
signal,
239242
next,
240243
...extraInputArgs,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as clientModule from './client';
2+
import { initContract } from './dsl';
3+
const c = initContract();
4+
describe('fetchApi', () => {
5+
afterEach(() => {
6+
jest.restoreAllMocks();
7+
});
8+
it('should not include content-type application/json if body is undefined', async () => {
9+
const tsRestApiStub = jest
10+
.spyOn(clientModule, 'tsRestFetchApi')
11+
.mockResolvedValue({
12+
status: 200,
13+
body: { message: 'never gonna give you up, never gonna let you down' },
14+
headers: new Headers(),
15+
});
16+
await clientModule.fetchApi({
17+
body: undefined,
18+
headers: {},
19+
path: '/rick-astley',
20+
clientArgs: {
21+
baseUrl: 'https://api.com',
22+
baseHeaders: {},
23+
},
24+
route: {
25+
method: 'POST',
26+
body: null,
27+
path: '/rick-astley',
28+
responses: {
29+
200: c.response<{ message: string }>(),
30+
},
31+
},
32+
query: {},
33+
extraInputArgs: {},
34+
});
35+
expect(tsRestApiStub).toHaveBeenCalledWith({
36+
body: undefined,
37+
contentType: undefined,
38+
credentials: undefined,
39+
headers: {},
40+
method: 'POST',
41+
next: undefined,
42+
path: '/rick-astley',
43+
rawBody: undefined,
44+
rawQuery: {},
45+
route: {
46+
body: null,
47+
method: 'POST',
48+
path: '/rick-astley',
49+
responses: {
50+
'200': expect.anything(),
51+
},
52+
},
53+
signal: undefined,
54+
});
55+
});
56+
57+
it('should include content-type application/json if body is defined', async () => {
58+
const tsRestApiStub = jest
59+
.spyOn(clientModule, 'tsRestFetchApi')
60+
.mockResolvedValue({
61+
status: 200,
62+
body: { message: 'never gonna give you up, never gonna let you down' },
63+
headers: new Headers(),
64+
});
65+
await clientModule.fetchApi({
66+
body: {
67+
message: 'never gonna say goodbye, never gonna tell a lie and hurt you',
68+
},
69+
headers: {},
70+
path: '/rick-astley',
71+
clientArgs: {
72+
baseUrl: 'https://api.com',
73+
baseHeaders: {},
74+
},
75+
route: {
76+
method: 'POST',
77+
body: null,
78+
path: '/rick-astley',
79+
responses: {
80+
200: c.response<{ message: string }>(),
81+
},
82+
},
83+
query: {},
84+
extraInputArgs: {},
85+
});
86+
expect(tsRestApiStub).toHaveBeenCalledWith(
87+
expect.objectContaining({
88+
body: '{"message":"never gonna say goodbye, never gonna tell a lie and hurt you"}',
89+
contentType: 'application/json',
90+
credentials: undefined,
91+
headers: {
92+
'content-type': 'application/json',
93+
},
94+
method: 'POST',
95+
next: undefined,
96+
path: '/rick-astley',
97+
rawBody: {
98+
message:
99+
'never gonna say goodbye, never gonna tell a lie and hurt you',
100+
},
101+
rawQuery: {},
102+
signal: undefined,
103+
})
104+
);
105+
});
106+
});

0 commit comments

Comments
 (0)