I have a NextJS frontend and ExpressJS backend. On login or signup, I send the cookies as token and set them on client side. Any further requests need this token to be included as cookie as Request Header. But they are not being included and I am getting an unauthorized no token provided error.
Client side is deployed in vercel and server on digital ocean. When I login via postman and send the request, it works in postman as the cookie header is included by default but it is not being included by the browser. I am using redux toolkit and rtk query to fecth and store api requests.
https://backend-url.ondigitalocean.app/api/v1/users/profile
strict-origin-when-cross-origin
<code>Request URL:
https://backend-url.ondigitalocean.app/api/v1/users/profile
Request Method:
GET
Status Code:
401 Unauthorized
Remote Address:
162.159.140.98:443
Referrer Policy:
strict-origin-when-cross-origin
</code>
Request URL:
https://backend-url.ondigitalocean.app/api/v1/users/profile
Request Method:
GET
Status Code:
401 Unauthorized
Remote Address:
162.159.140.98:443
Referrer Policy:
strict-origin-when-cross-origin
Response Headers:
<code>access-control-allow-credentials:
access-control-allow-origin:
https://frontend-url.vercel.app
default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
application/json; charset=utf-8
cross-origin-opener-policy:same-origin
cross-origin-resource-policy:same-origin
date:Sat, 10 Aug 2024 17:39:37 GMT
etag:W/"1f-pZK48A3ZbwxVl8UtZcC9y8sBFbw"
referrer-policy:no-referrer
__cf_bm=WisMDqoOalzkLe8l8ZzqtFhzJk489UbjK8Z7C9H9m5g-1723311577-1.0.1.1-QWuqSLSeAB8d4pcM_Pqc01wlQ4fOMmpB8fDUpfu2aL2FHkm5AeSgyf7Bpo.AoA7JLY0IqYWfOlv2Ucmikxj.zA; path=/; expires=Sat, 10-Aug-24 18:09:37 GMT; domain=.ondigitalocean.app; HttpOnly; Secure; SameSite=None
strict-transport-security:
max-age=15552000; includeSubDomains
vary:Origin, Accept-Encoding
x-content-type-options:nosniff
x-dns-prefetch-control:off
x-do-app-origin:ea20cc1b-573c-4676-8e91-f16863ea1b36
x-download-options:noopen
x-frame-options:SAMEORIGIN
x-permitted-cross-domain-policies:none
<code>access-control-allow-credentials:
true
access-control-allow-origin:
https://frontend-url.vercel.app
cache-control:
private
cf-cache-status:
MISS
cf-ray:
8b11c42e080ca931-MAA
content-length:
31
content-security-policy:
default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
content-type:
application/json; charset=utf-8
cross-origin-opener-policy:same-origin
cross-origin-resource-policy:same-origin
date:Sat, 10 Aug 2024 17:39:37 GMT
etag:W/"1f-pZK48A3ZbwxVl8UtZcC9y8sBFbw"
origin-agent-cluster:?1
referrer-policy:no-referrer
server:cloudflare
set-cookie:
__cf_bm=WisMDqoOalzkLe8l8ZzqtFhzJk489UbjK8Z7C9H9m5g-1723311577-1.0.1.1-QWuqSLSeAB8d4pcM_Pqc01wlQ4fOMmpB8fDUpfu2aL2FHkm5AeSgyf7Bpo.AoA7JLY0IqYWfOlv2Ucmikxj.zA; path=/; expires=Sat, 10-Aug-24 18:09:37 GMT; domain=.ondigitalocean.app; HttpOnly; Secure; SameSite=None
strict-transport-security:
max-age=15552000; includeSubDomains
vary:Origin, Accept-Encoding
x-content-type-options:nosniff
x-dns-prefetch-control:off
x-do-app-origin:ea20cc1b-573c-4676-8e91-f16863ea1b36
x-do-orig-status:401
x-download-options:noopen
x-frame-options:SAMEORIGIN
x-permitted-cross-domain-policies:none
x-xss-protection: 0
</code>
access-control-allow-credentials:
true
access-control-allow-origin:
https://frontend-url.vercel.app
cache-control:
private
cf-cache-status:
MISS
cf-ray:
8b11c42e080ca931-MAA
content-length:
31
content-security-policy:
default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
content-type:
application/json; charset=utf-8
cross-origin-opener-policy:same-origin
cross-origin-resource-policy:same-origin
date:Sat, 10 Aug 2024 17:39:37 GMT
etag:W/"1f-pZK48A3ZbwxVl8UtZcC9y8sBFbw"
origin-agent-cluster:?1
referrer-policy:no-referrer
server:cloudflare
set-cookie:
__cf_bm=WisMDqoOalzkLe8l8ZzqtFhzJk489UbjK8Z7C9H9m5g-1723311577-1.0.1.1-QWuqSLSeAB8d4pcM_Pqc01wlQ4fOMmpB8fDUpfu2aL2FHkm5AeSgyf7Bpo.AoA7JLY0IqYWfOlv2Ucmikxj.zA; path=/; expires=Sat, 10-Aug-24 18:09:37 GMT; domain=.ondigitalocean.app; HttpOnly; Secure; SameSite=None
strict-transport-security:
max-age=15552000; includeSubDomains
vary:Origin, Accept-Encoding
x-content-type-options:nosniff
x-dns-prefetch-control:off
x-do-app-origin:ea20cc1b-573c-4676-8e91-f16863ea1b36
x-do-orig-status:401
x-download-options:noopen
x-frame-options:SAMEORIGIN
x-permitted-cross-domain-policies:none
x-xss-protection: 0
Request Headers: (Note “Cookie” header is absent)
<code>:authority:backend-url.ondigitalocean.app:method:
:path:/api/v1/users/profile
accept-encoding:gzip, deflate, br, zstd
accept-language:en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7
origin:https://bitkart-client.vercel.app
referer:https://bitkart-client.vercel.app/
sec-ch-ua:"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"
sec-ch-ua-platform:"Android"
sec-fetch-site:cross-site
Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36
layout-d57fc628599fd89c.js:1 All Cookies:
{token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2Y…zY2fQ.SQpo8YdVug1_r7cUrtf8vam0jV-KpTxQz9N4WoaOPuU'}
9858-045d99ed3b66ee7e.js:1
GET https://king-prawn-app-amyex.ondigitalocean.app/api/v1/users/profile 401 (Unauthorized)
<code>:authority:backend-url.ondigitalocean.app:method:
GET
:path:/api/v1/users/profile
:scheme:https
accept:*/*
accept-encoding:gzip, deflate, br, zstd
accept-language:en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7
dnt:1
origin:https://bitkart-client.vercel.app
priority:u=1, i
referer:https://bitkart-client.vercel.app/
sec-ch-ua:"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"
sec-ch-ua-mobile:?1
sec-ch-ua-platform:"Android"
sec-fetch-dest:empty
sec-fetch-mode:cors
sec-fetch-site:cross-site
sec-gpc:1
user-agent:
Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36
layout-d57fc628599fd89c.js:1 All Cookies:
{token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2Y…zY2fQ.SQpo8YdVug1_r7cUrtf8vam0jV-KpTxQz9N4WoaOPuU'}
9858-045d99ed3b66ee7e.js:1
GET https://king-prawn-app-amyex.ondigitalocean.app/api/v1/users/profile 401 (Unauthorized)
</code>
:authority:backend-url.ondigitalocean.app:method:
GET
:path:/api/v1/users/profile
:scheme:https
accept:*/*
accept-encoding:gzip, deflate, br, zstd
accept-language:en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7
dnt:1
origin:https://bitkart-client.vercel.app
priority:u=1, i
referer:https://bitkart-client.vercel.app/
sec-ch-ua:"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"
sec-ch-ua-mobile:?1
sec-ch-ua-platform:"Android"
sec-fetch-dest:empty
sec-fetch-mode:cors
sec-fetch-site:cross-site
sec-gpc:1
user-agent:
Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36
layout-d57fc628599fd89c.js:1 All Cookies:
{token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2Y…zY2fQ.SQpo8YdVug1_r7cUrtf8vam0jV-KpTxQz9N4WoaOPuU'}
9858-045d99ed3b66ee7e.js:1
GET https://king-prawn-app-amyex.ondigitalocean.app/api/v1/users/profile 401 (Unauthorized)
apislice:
<code>import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import Cookies from "js-cookie";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
prepareHeaders: (headers) => {
// Add content-type header
headers.set("Content-Type", "application/json");
// Add CORS credentials header
headers.set("Access-Control-Allow-Credentials", "true");
const token = Cookies.get("token");
headers.set("Authorization", `Bearer ${token}`);
export const apiSlice = createApi({
endpoints: (builder) => ({
register: builder.mutation<
{ email: string; password: string; firstName: string; lastName: string }
login: builder.mutation<any, { email: string; password: string }>({
verifyOtp: builder.mutation<
{ userId: string; otp: string }
query: ({ userId, otp }) => ({
url: `auth/verify-otp/${userId}`,
body: { otp: Number(otp) },
resendOtp: builder.mutation<{ message: string }, { userId: string }>({
<code>import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import Cookies from "js-cookie";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
prepareHeaders: (headers) => {
// Add content-type header
headers.set("Content-Type", "application/json");
// Add CORS credentials header
headers.set("Access-Control-Allow-Credentials", "true");
const token = Cookies.get("token");
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
return headers;
},
});
export const apiSlice = createApi({
reducerPath: "api",
baseQuery,
endpoints: (builder) => ({
register: builder.mutation<
any,
{ email: string; password: string; firstName: string; lastName: string }
>({
query: (body) => ({
url: "auth/register",
method: "POST",
body,
}),
}),
login: builder.mutation<any, { email: string; password: string }>({
query: (body) => ({
url: "auth/login",
method: "POST",
body,
}),
}),
verifyOtp: builder.mutation<
{
message: string;
user: {
firstName: string;
lastName: string;
email: string;
_id: string;
token: string;
};
},
{ userId: string; otp: string }
>({
query: ({ userId, otp }) => ({
url: `auth/verify-otp/${userId}`,
method: "POST",
body: { otp: Number(otp) },
}),
}),
resendOtp: builder.mutation<{ message: string }, { userId: string }>({
query: (body) => ({
url: "auth/resend-otp",
method: "POST",
body,
}),
}),
}),
});
export const {
useRegisterMutation,
useLoginMutation,
useVerifyOtpMutation,
useResendOtpMutation,
} = apiSlice;
</code>
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import Cookies from "js-cookie";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
prepareHeaders: (headers) => {
// Add content-type header
headers.set("Content-Type", "application/json");
// Add CORS credentials header
headers.set("Access-Control-Allow-Credentials", "true");
const token = Cookies.get("token");
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
return headers;
},
});
export const apiSlice = createApi({
reducerPath: "api",
baseQuery,
endpoints: (builder) => ({
register: builder.mutation<
any,
{ email: string; password: string; firstName: string; lastName: string }
>({
query: (body) => ({
url: "auth/register",
method: "POST",
body,
}),
}),
login: builder.mutation<any, { email: string; password: string }>({
query: (body) => ({
url: "auth/login",
method: "POST",
body,
}),
}),
verifyOtp: builder.mutation<
{
message: string;
user: {
firstName: string;
lastName: string;
email: string;
_id: string;
token: string;
};
},
{ userId: string; otp: string }
>({
query: ({ userId, otp }) => ({
url: `auth/verify-otp/${userId}`,
method: "POST",
body: { otp: Number(otp) },
}),
}),
resendOtp: builder.mutation<{ message: string }, { userId: string }>({
query: (body) => ({
url: "auth/resend-otp",
method: "POST",
body,
}),
}),
}),
});
export const {
useRegisterMutation,
useLoginMutation,
useVerifyOtpMutation,
useResendOtpMutation,
} = apiSlice;
userslice:
<code>import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
export const userApi = createApi({
endpoints: (builder) => ({
getUserProfile: builder.query({
query: () => "users/profile",
updateUserProfile: builder.mutation({
query: (updatedUser) => ({
invalidatesTags: ["User"],
getUserItems: builder.query({
query: () => "/users/items",
getUserChats: builder.query({
query: () => "/users/chats",
useUpdateUserProfileMutation,
<code>import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
credentials: "include",
});
export const userApi = createApi({
reducerPath: "userApi",
baseQuery,
tagTypes: ["User"],
endpoints: (builder) => ({
getUserProfile: builder.query({
query: () => "users/profile",
providesTags: ["User"],
}),
updateUserProfile: builder.mutation({
query: (updatedUser) => ({
url: "users/profile",
method: "PUT",
body: updatedUser,
}),
invalidatesTags: ["User"],
}),
getUserItems: builder.query({
query: () => "/users/items",
providesTags: ["User"],
}),
getUserChats: builder.query({
query: () => "/users/chats",
providesTags: ["User"],
}),
}),
});
export const {
useGetUserProfileQuery,
useUpdateUserProfileMutation,
useGetUserItemsQuery,
useGetUserChatsQuery,
} = userApi;
</code>
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
credentials: "include",
});
export const userApi = createApi({
reducerPath: "userApi",
baseQuery,
tagTypes: ["User"],
endpoints: (builder) => ({
getUserProfile: builder.query({
query: () => "users/profile",
providesTags: ["User"],
}),
updateUserProfile: builder.mutation({
query: (updatedUser) => ({
url: "users/profile",
method: "PUT",
body: updatedUser,
}),
invalidatesTags: ["User"],
}),
getUserItems: builder.query({
query: () => "/users/items",
providesTags: ["User"],
}),
getUserChats: builder.query({
query: () => "/users/chats",
providesTags: ["User"],
}),
}),
});
export const {
useGetUserProfileQuery,
useUpdateUserProfileMutation,
useGetUserItemsQuery,
useGetUserChatsQuery,
} = userApi;
itemSlice:
<code>import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import Cookies from "js-cookie";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
prepareHeaders: (headers) => {
console.log("All Cookies:", Cookies.get());
const token = Cookies.get("token");
headers.set("Authorization", `Bearer ${token}`);
export const itemApi = createApi({
endpoints: (builder) => ({
getAllItems: builder.query({
//console.log("Params:", params);
const queryParams = new URLSearchParams();
if (search) queryParams.append("search", search);
if (category) queryParams.append("category", category);
if (minPrice) queryParams.append("minPrice", minPrice.toString());
if (maxPrice) queryParams.append("maxPrice", maxPrice.toString());
if (page) queryParams.append("page", page.toString());
if (limit) queryParams.append("limit", limit.toString());
if (format) queryParams.append("format", format);
if (width) queryParams.append("width", width.toString());
if (height) queryParams.append("height", height.toString());
if (quality) queryParams.append("quality", quality.toString());
const queryString = `item/all?${queryParams.toString()}`;
//console.log("Query String:", queryString.toString());
getItemsOfUser: builder.query({
getItemById: builder.query({
query: (id) => `item/${id}?format=webp&quality=80`,
createItem: builder.mutation({
invalidatesTags: ["Items"],
updateItem: builder.mutation({
query: ({ id, updatedItem }) => ({
invalidatesTags: ["Items"],
deleteItem: builder.mutation({
invalidatesTags: ["Items"],
updateImages: builder.mutation({
query: ({ id, formData }) => ({
url: `item/${id}/images`,
invalidatesTags: ["Items"],
deleteImage: builder.mutation({
query: ({ itemId, imageId }) => ({
url: `item/${itemId}/images/${imageId}`,
invalidatesTags: ["Items"],
<code>import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import Cookies from "js-cookie";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
credentials: "include",
prepareHeaders: (headers) => {
// Log all cookies
console.log("All Cookies:", Cookies.get());
const token = Cookies.get("token");
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
return headers;
},
});
export const itemApi = createApi({
reducerPath: "itemApi",
baseQuery,
tagTypes: ["Items"],
endpoints: (builder) => ({
getAllItems: builder.query({
query: (params) => {
const {
search,
category,
minPrice,
maxPrice,
page,
limit,
format,
width,
height,
quality,
} = params;
//console.log("Params:", params);
const queryParams = new URLSearchParams();
if (search) queryParams.append("search", search);
if (category) queryParams.append("category", category);
if (minPrice) queryParams.append("minPrice", minPrice.toString());
if (maxPrice) queryParams.append("maxPrice", maxPrice.toString());
if (page) queryParams.append("page", page.toString());
if (limit) queryParams.append("limit", limit.toString());
if (format) queryParams.append("format", format);
if (width) queryParams.append("width", width.toString());
if (height) queryParams.append("height", height.toString());
if (quality) queryParams.append("quality", quality.toString());
const queryString = `item/all?${queryParams.toString()}`;
//console.log("Query String:", queryString.toString());
return queryString;
},
providesTags: ["Items"],
}),
getItemsOfUser: builder.query({
query: () => `item/`,
providesTags: ["Items"],
}),
getItemById: builder.query({
query: (id) => `item/${id}?format=webp&quality=80`,
providesTags: ["Items"],
}),
createItem: builder.mutation({
query: (newItem) => ({
url: "item/",
method: "POST",
body: newItem,
}),
invalidatesTags: ["Items"],
}),
updateItem: builder.mutation({
query: ({ id, updatedItem }) => ({
url: `item/${id}`,
method: "PUT",
body: updatedItem,
}),
invalidatesTags: ["Items"],
}),
deleteItem: builder.mutation({
query: (id) => ({
url: `item/${id}`,
method: "DELETE",
}),
invalidatesTags: ["Items"],
}),
updateImages: builder.mutation({
query: ({ id, formData }) => ({
url: `item/${id}/images`,
method: "PATCH",
body: formData,
}),
invalidatesTags: ["Items"],
}),
deleteImage: builder.mutation({
query: ({ itemId, imageId }) => ({
url: `item/${itemId}/images/${imageId}`,
method: "DELETE",
}),
invalidatesTags: ["Items"],
}),
}),
});
export const {
useGetAllItemsQuery,
useGetItemByIdQuery,
useCreateItemMutation,
useUpdateItemMutation,
useDeleteItemMutation,
useUpdateImagesMutation,
useDeleteImageMutation,
useGetItemsOfUserQuery,
} = itemApi;
</code>
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import Cookies from "js-cookie";
const baseQuery = fetchBaseQuery({
baseUrl: `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/`,
credentials: "include",
prepareHeaders: (headers) => {
// Log all cookies
console.log("All Cookies:", Cookies.get());
const token = Cookies.get("token");
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
return headers;
},
});
export const itemApi = createApi({
reducerPath: "itemApi",
baseQuery,
tagTypes: ["Items"],
endpoints: (builder) => ({
getAllItems: builder.query({
query: (params) => {
const {
search,
category,
minPrice,
maxPrice,
page,
limit,
format,
width,
height,
quality,
} = params;
//console.log("Params:", params);
const queryParams = new URLSearchParams();
if (search) queryParams.append("search", search);
if (category) queryParams.append("category", category);
if (minPrice) queryParams.append("minPrice", minPrice.toString());
if (maxPrice) queryParams.append("maxPrice", maxPrice.toString());
if (page) queryParams.append("page", page.toString());
if (limit) queryParams.append("limit", limit.toString());
if (format) queryParams.append("format", format);
if (width) queryParams.append("width", width.toString());
if (height) queryParams.append("height", height.toString());
if (quality) queryParams.append("quality", quality.toString());
const queryString = `item/all?${queryParams.toString()}`;
//console.log("Query String:", queryString.toString());
return queryString;
},
providesTags: ["Items"],
}),
getItemsOfUser: builder.query({
query: () => `item/`,
providesTags: ["Items"],
}),
getItemById: builder.query({
query: (id) => `item/${id}?format=webp&quality=80`,
providesTags: ["Items"],
}),
createItem: builder.mutation({
query: (newItem) => ({
url: "item/",
method: "POST",
body: newItem,
}),
invalidatesTags: ["Items"],
}),
updateItem: builder.mutation({
query: ({ id, updatedItem }) => ({
url: `item/${id}`,
method: "PUT",
body: updatedItem,
}),
invalidatesTags: ["Items"],
}),
deleteItem: builder.mutation({
query: (id) => ({
url: `item/${id}`,
method: "DELETE",
}),
invalidatesTags: ["Items"],
}),
updateImages: builder.mutation({
query: ({ id, formData }) => ({
url: `item/${id}/images`,
method: "PATCH",
body: formData,
}),
invalidatesTags: ["Items"],
}),
deleteImage: builder.mutation({
query: ({ itemId, imageId }) => ({
url: `item/${itemId}/images/${imageId}`,
method: "DELETE",
}),
invalidatesTags: ["Items"],
}),
}),
});
export const {
useGetAllItemsQuery,
useGetItemByIdQuery,
useCreateItemMutation,
useUpdateItemMutation,
useDeleteItemMutation,
useUpdateImagesMutation,
useDeleteImageMutation,
useGetItemsOfUserQuery,
} = itemApi;
The backend code is in this repo: text
Client repo: text