Uploading file to a server via the next js api route - reactjs

I am using the Next.js api as a middleware before transferring the requests to the server, I am trying to send a multipart/formdata request with a file, it works if I call the backend API directly from the client-side with a FormData object, I wrote the Next API to parse that form data, form a new form data (server side this time) and call the backend API but that fails.
Here is the code:
import axios from "axios";
import formidable from "formidable";
import FormData from "form-data";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
//
import BlogAPIs from "utils/apis/BlogAPIs";
export const config = {
api: {
bodyParser: false,
},
};
export default async (req: NextApiRequest, res: NextApiResponse) => {
try {
const session = await getSession({ req });
const formData = new FormData();
const fs = require("fs");
const data: { fields: any; files: any } = await new Promise(
(resolve, reject) => {
const form = new formidable.IncomingForm();
form.parse(req, (err: any, fields: any, files: any) => {
if (err) reject({ err });
resolve({ fields, files });
});
}
);
["title", "content", "description", "thumbnail"].map((key) => {
data.fields[key] && formData.append(key, data.fields[key]);
data.files[key] &&
formData.append(key, fs.createReadStream(data.files[key].filepath));
});
let config = {
method: "post",
url: `${process.env.API_BASE_URL}/blogs/`,
headers: {
Authorization: `Bearer ${session?.backendToken as string}`,
...formData.getHeaders(),
},
data: formData,
};
await axios(config);
res.status(200).json("Succesfully added blog");
} catch (error: any) {
res.status(700).json(error.message);
}
};
I can't seem to figure out what I am doing wrong here...

This is how I was able to achieve it:
import axios from "axios";
import formidable from "formidable";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
import { processError } from "utils/apis/processError";
//
export const config = {
api: {
bodyParser: false,
},
};
export default async (req: NextApiRequest, res: NextApiResponse) => {
try {
const FormData = require("form-data");
const concat = require("concat-stream");
const fs = require("fs");
const session = await getSession({ req });
const data: { fields: any; files: any } = await new Promise(
(resolve, reject) => {
const form = new formidable.IncomingForm({
keepExtensions: true,
});
form.parse(req, (err: any, fields: any, files: any) => {
if (err) reject({ err });
resolve({ fields, files });
});
}
);
const promise = new Promise<{ data: any; headers: any }>((resolve) => {
const formData = new FormData();
["title", "content", "description", "tags"].map((key) => {
data.fields[key] && formData.append(key, data.fields[key]);
});
data.files["thumbnail"] &&
formData.append(
"thumbnail",
fs.createReadStream(data.files["thumbnail"].filepath),
data.files["thumbnail"].originalFilename
);
formData.pipe(
concat({ encoding: "buffer" }, (data: any) =>
resolve({ data, headers: formData.getHeaders() })
)
);
});
promise
.then(({ data, headers }) =>
axios.post(`${process.env.API_BASE_URL}/blogs/`, data, {
headers: {
Authorization: `Bearer ${session?.backendToken as string}`,
...headers,
},
})
)
.catch((error) => {
const errorMessage = processError(error);
res.status(700).json(errorMessage);
})
.then((response) => {
res.status(200).json({
slug: response?.data.slug,
});
});
} catch (error: any) {
res.status(700).json(error.message);
}
};
I created a promise with the streaming data and then sent the same to my server.

Related

Error Cannot set headers after they are sent to the client

I got this error whenever I try to log In using Google Login API
In my console I get this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
and in the screen I get white page with this error:
InternalOAuthError: Failed to fetch user profile
I'm using two Login method, one is normal and one using Passport JS
Login.jsx Login Page
import { useEffect, useContext, useRef } from "react";
import { Context } from "../../context/Context";
import axios from "axios";
import { useState } from "react"
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { dispatch, isFetching } = useContext(Context);
const [error, setError] = useState(false);
// FOR LOGIN
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/login", {
email: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
}
};
// FOR GOOGLE LOGIN
useEffect(() => {
fetch(`http://localhost:4000/login/success`, {
method: 'GET',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true,
},
})
.then((response) => {
dispatch({ type: "LOGIN_START" });
if (response.status === 200) return response.json();
throw new Error('failed to authenticate user');
})
.then((responseJson) => {
dispatch({ type: "LOGIN_SUCCESS", payload: responseJson.data });
})
.catch((error) => {
dispatch({ type: "LOGIN_FAILURE" });
// eslint-disable-next-line no-console
console.error("Failed to authenticate user", error)
});
}, []);
const google = () => {
window.open("http://localhost:4000/auth/google/callback", "_self");
};
return()
}
auth.js Route:
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
error: false,
message: "succesfull",
user: req.user,
cookies: req.cookies
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});

Redux Saga call api before token is set

I m trying to implements a react application with authentification using keycloak, all sounds good but when I refresh the page and there is fetching of an api, Saga perform the call before the token is set
there is my saga call
function* getAPI(action) {
const state = yield select();
try {
let response = yield call(
axiosRequest,
"get",
BaseURL,
`/foo/mini`,
{},
setAuthorizationBearer(state.auth.token),
{ sendToken: true },
"application/json"
);
yield put({ type: `${action.type}_SUCCESS`, payload: response, metadata: action.metadata })
} catch (e) {
yield put({ type: `${action.type}_ERROR`, payload: e })
}
}
and here is my axios request instance
import axios from "axios";
let authorizationBearer = null;
export const setAuthorizationBearer = token => {
authorizationBearer = token;
};
const instance = (
method,
baseURL = process.env.REACT_APP_ENDPOINT,
url,
data = null,
headers = null,
sendToken = true,
contentType
) => {
return new Promise((resolve, reject) => {
const p = {
sendToken: sendToken.sendToken,
data: {
...data,
},
};
const req = axios.create({
method,
baseURL,
url,
timeout: 30000,
headers: headers,
crossDomain: true,
});
headers = {};
if (p.sendToken && authorizationBearer) {
headers.Authorization = `Bearer ${authorizationBearer}`;
headers["Content-Type"] = contentType;
}
req({
method,
baseURL,
url,
data,
headers,
sendToken,
})
.then((payload) => {
if (payload) {
if (payload.status < 400) {
resolve(payload);
} else {
reject(payload);
}
} else {
reject(payload);
}
})
.catch((e) => {
if (axios.isCancel(e)) {
console.log("Request canceled", e.message);
} else {
// handle error
}
reject(e);
});
});
};
export default instance;
And finally i set my token on authentification with a dispatch
const dispatch = useDispatch()
<ReactKeycloakProvider onTokens={({token}) => dispatch(authUser(token))} authClient={Keycloak(config)}
initOptions={{
onLoad: 'login-required',
checkLoginIframe: false,
timeSkew: "0",
refreshToken: ""
}}
LoadingComponent={<div />}
>
....
</ReactKeycloakProvider>
Most probably the application content is being rendered before the onTokens is being executed. Try checking on the existence of the token in the store state before rendering anything (or show a loading screen).

Upload input form data and file/image Next js

I am trying to send form data life person name, email and image together using Next js. I used formdata for file upload and using react-hook-form for form input.
The problem is I couldn't receive the image/file in the Next api.
My codes are :
Onchange:
const handleImgChange = (e) => {
if (e.target.files && e.target.files[0]) {
const img = e.target.files[0];
setProfileImg(img);
}
};
to get form data from input.
const handleIChange = (e) => {
const value = e.target.value;
setContents((prevContnet) => {
return {
...prevContnet,
[e.target.name]: value,
};
});
};
On submit
const handleOnsubmlit = (e) => {
e.preventDefault();
if (profileImg.length > 0) {
const formData = { ...contents, profile_picture: profileImg };
updateUserSetting(formData);
} else {
updateUserSetting(contents);
}
};
updateUserSetting
async function updateUserSetting(formdata) {
try {
console.log("form datas", formdata);
dispatch({ type: "UPDATE_USER_SETTING_REQUEST" });
const { data } = await axios(
`${NEXT_URL}/api/updateusersetting`,
{
method: "PUT",
formdata,
"content-type": "multipart/form-data",
}
);
console.log("return data ", data[0]);
dispatch({ type: "UPDATE_USER_SETTING_SUCCESS", payload: data[0] });
} catch (error) {
dispatch({
type: "UPDATE_USER_SETTING_FAIL",
payload: error.response
});
}
}
API
import { IncomingForm } from "formidable";
export const config = {
api: {
bodyParser: false,
},
};
export default async (req, res) => {
if (req.method === "PUT") {
if (!req.headers.cookie) {
res.status(403).json({ message: "Not Authorized" });
return;
}
const { token } = cookie.parse(req.headers.cookie);
console.log("body is", req.body);
const formData = await new Promise((req, res) => {
const form = new IncomingForm();
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
res.writeHead(200, { "content-type": "multipart/form-data" });
res.json({ fields, files });
});
});
};
how can I put data together and send it to the desired API? Thanks in advance.
You can use the FormData interface to send files and other fields as a single JSONified string, or individual strings. Formidable will separate your fields and files in the callback, and you can process them individually.
Here's a working Codesandbox.
Output:

fetchMock function makes actual API calls instead of mocking the requests

I am trying to test my signupAction shown below.
import axios from 'axios';
import actionTypes from '../action_types';
import { apiRequest } from '../common_dispatch';
export const signupAction = (user) => async (dispatch) => {
dispatch(apiRequest(true));
await axios
.post(`${process.env.REACT_APP_API_URL}/users`, { ...user }, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
dispatch(
{
type: actionTypes.REGISTER_SUCCESS,
payload: response.data.user,
},
);
dispatch(apiRequest(false));
})
.catch((error) => {
let errors = 'ERROR';
if (error.message === 'Network Error') {
errors = error.message;
} else {
errors = error.response.data.errors;
console.log(error);
}
dispatch(
{
type: actionTypes.REGISTER_FAIL,
payload: errors,
},
);
dispatch(apiRequest(false));
});
};
I figured I could mock the API call above using the fetchMock library. The problem is fetchMock makes actual calls hence the test passes in the first intance but fails when I run it the second time because the user I am trying to sign up already exists. my test is as shown below.
mport configureMockStore from 'redux-mock-store';
import * as actions from './signup.action';
import mocks from './mocks';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('signUp actions', () => {
afterEach(() => {
fetchMock.resetMocks();
console.log('yess bro am called')
})
it('dispatches create REGISTER_FAIL when signup has been done', async () => {
fetchMock.postOnce('/users', { ...mocks.user }, {
headers: { 'Content-Type': 'application/json' },
});
const expectedActions = [
{ type: 'API_REQUEST', payload: true },
{ type: 'REGISTER_FAIL', payload: { email: "Email karanilarrygmail.com is not a valid email" } },
{ type: 'API_REQUEST', payload: false },
]
const store = mockStore(mocks.user);
return store.dispatch(actions.signupAction(mocks.user)).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
});
mocks.user is an object containing the user signup data.
What am I doing wrong

Branch coverage zero percent in jest

I have written some test cases and everything seems fine except the following one. I am getting zero branch cover for one file. I have googled couple of blog and I came to understand if the statement cab be executed in multiple scenario that call branch coverage. But I don't find my code can be executed in multiple way.
request.js
import axios from 'axios';
export default async (request, httpService = axios) => {
const {
method, url, data, headers,
} = request;
return httpService.request({
method,
url,
headers: Object.assign({}, headers),
data,
});
};
reqeust.test.js
describe('requestServie', () => {
it('should have a valid request object', async () => {
const requestObj = {
method: 'POST',
url: 'http://mock.url',
data: {},
};
const mockRequest = jest.fn(() => Promise.resolve({}));
const httpService = {
request: mockRequest,
};
await request(requestObj, httpService);
expect(mockRequest).toHaveBeenCalledWith({
method: requestObj.method,
url: requestObj.url,
headers: {},
data: requestObj.data,
});
});
it('should return a valid response (empty)', async () => {
const response = {
data: {
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {},
};
const mockRequest = jest.fn(() => Promise.resolve(response));
const httpService = {
request: mockRequest,
};
const res = await request({ url: 'http://mock.url' }, httpService);
expect(res).not.toBe(null);
expect(res).toMatchObject(
{
status: response.status,
},
);
});
});
Edit
rquest.js
export default async (request, httpService = axios) => {
const {
method, url, data, headers,
} = request;
return httpService.request({
method,
url,
headers: Object.assign({}, headers),
data,
}).then(successResponse, (error) => {
throwHttpError(error);
});
};
request.test.js
import HttpError from 'standard-http-error';
import axios from 'axios';
import request, { successResponse, throwHttpError } from './requestService';
describe('requestService', () => {
jest.mock('axios', () => ({
request: jest.fn(() => Promise.resolve({})),
}));
describe('successResponse', () => {
const mockRes = {
status: 9001,
data: {
stuff: 'stuff',
},
};
it('should returns an object with only status and data properties', () => {
const responseKeys = Object.keys(successResponse(mockRes));
expect(responseKeys).toMatchObject(['status', 'data']);
expect(responseKeys.length).toBe(2);
});
it('should map the status of the reponse to the status property', () => {
expect(successResponse(mockRes).status)
.toBe(mockRes.status);
});
it('should map the data of the reponse to the data property', () => {
expect(successResponse(mockRes).data)
.toMatchObject(mockRes.data);
});
it('should have a valid request object', async () => {
const requestObj = {
method: 'POST',
url: 'http://mock.url',
data: {},
headers: {},
};
const mockRequest = jest.fn(() => Promise.resolve({}));
const httpService = {
request: mockRequest,
};
await request(requestObj, httpService);
expect(mockRequest).toHaveBeenCalledWith({
method: requestObj.method,
url: requestObj.url,
headers: {},
data: requestObj.data,
});
});
});
describe('httpThrowError', () => {
const mockErr = {
response: {
status: 9001,
statusText: 'error message goes here',
},
};
it('should map the status of the reponse to the error.status property', () => {
try {
throwHttpError(mockErr);
} catch (e) {
expect(e).not.toBe(null);
expect(e.status).toBe(mockErr.response.status);
expect(e.message).toBe(mockErr.response.statusText);
}
});
it('should map the data of the reponse to the error.data property', () => {
const mockErrWithData = Object.assign({}, mockErr);
mockErrWithData.response.data = {};
try {
throwHttpError(mockErrWithData);
} catch (e) {
expect(e).not.toBe(null);
expect(e.data).toBe(mockErrWithData.response.data);
}
});
});
describe('request', () => {
const testCases = [
['should return error response on server error', 500],
['should return error response on bad request', 400],
['should return error response on unauthorised', 401],
];
testCases.forEach(([testName, errorStatus]) => {
it(testName, async () => {
const errorResponse = {
response: {
status: errorStatus,
},
};
const mockRequest = jest.fn(() => Promise.reject(errorResponse));
const httpService = {
request: mockRequest,
};
try {
await request({ url: 'http://mock.url' }, httpService);
throw new Error('Expected an exception, but none was thrown');
} catch (err) {
expect(err).not.toBe(null);
expect(err).toMatchObject(
new HttpError(errorResponse.response.status,
errorResponse.response.statusText),
);
}
});
});
it('should return an valid response (empty)', async () => {
const response = {
data: {
meta: {},
results: [],
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {},
};
const mockRequest = jest.fn(() => Promise.resolve(response));
const httpService = {
request: mockRequest,
};
const res = await request({ url: 'http://mock.url' }, httpService);
expect(res).not.toBe(null);
expect(res).toMatchObject(
{
status: response.status,
data: response.data,
},
);
});
it('should use axios by default', async () => {
const req = { url: 'http://mock.url', method: 'get' };
await request(req);
expect(axios.request).toHaveBeenCalled();
});
});
});
Error
Updated 15/Nov/18
"jest": "^23.6.0",
import HttpError from 'standard-http-error';
import axios from 'axios';
import request, { successResponse, throwHttpError } from './requestService';
jest.mock('axios', () => ({
request: jest.fn(),
}));
Error
To see what is not covered you can go to coverage/Iconv-report and open index.html. Those are created once you run jest with --coverage option.
It looks like uncovered branch is: httpService = axios. So you need to check if default axios is used.
To cover that you may run request without httpService argument - you can mock axios globally for that, i.e.:
import axios from 'axios';
// note that mock goes before any describe block
jest.mock('axios', () => {
return {
request: jest.fn(() => Promise.resolve({})),
}
});
describe('requestService', () => {
// ... your other tests
it('should use axios by default', async () => {
const opts = { url: 'http://mock.url', method: 'get' };
const res = await request(opts);
expect(axios.request).toHaveBeenCalled();
});
});
Note that jest.mock have some buggy behavior when running inside a spec.

Resources