Sending FormData through 2 API's (NextJS) - node.js

So I'm sending some formdata, including images to my backend. To obfuscate/secure my backend even more - I'm learning that it's good practice to take advantage of the NextJS api (send calls from client to NextJS server/api, NextJS api acts as a middleman and sends data to backend api). I'm also using Auth0 - and the most secure way to get tokens is via the NextJS api itself.
I have no issues reading the data on my backend when I make an api call directly from client to backend. However, I'm having issues actually getting the data (primarily the images/files) OUT of the request on my NextJS api for repackaging and sending to my actual backend. I can't use FormData within the NextJS api either, so I'm just confused all around.
Client submit function:
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("title", brand.name);
formData.append("slug", brand.slug);
formData.append("bio", brand.bio);
formData.append("logo", brand.logo[0]);
const response = await fetch("/api/brands", {
method: "post",
body: formData,
});
});
NextJS api code (does not work - I am getting an access token perfectly, it's just the sending of the formdata/files I'm stuck on)
import { getAccessToken, withApiAuthRequired } from "#auth0/nextjs-auth0";
export default withApiAuthRequired(async function products(req, res) {
const { accessToken } = await getAccessToken(req, res, {});
const response = await fetch("http://localhost:5000/brands", {
method: "post",
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: {
title: req.body.title,
slug: req.body.slug,
bio: req.body.bio,
logo: req.file,
},
});
const products = await response.json();
res.status(200);
});

You might be missing the "Content-Type: multipart/form-data" on the 2nd API call that you make. On the client side it's inferred from the FormData object, but on the server side you have to specify it manually. Altough, I couldn't find a way to do that on the server. This could indicate that the use case is wrong, I think making another request to your own server is a no-no. Next.js is meant to use this function called "withApiAuthRequired" as a wrapper, from which you call your services, or business logic. There is no need to make a second HTTP Request here.

Related

Axios - Prevent sending JWT token on external api calls

I'm building a fullstack app with nuxt + express and I have finally managed to include an authentication between my frontend/backend with passport and jwt.
I want to make additional api requests to my own github repo for fetching the latest releases (so a user gets a information that an update exists). This requets failed with a "Bad credentials" messages. I think this happens because my jwt token is sent with it (I can see my token in the request header).
My question is, is it possible to prevent axios from sending my JWT token in only this call? First, to make my request work and second, I don't want the token to be sent in external requests.
Example:
const url = 'https://api.github.com/repos/xxx/releases/latest'
this.$axios.get(url)
.then((res) => {
this.latestRelease = res.data
}).catch((error) => {
console.error(error)
})
transformRequest
You can override the Authorization for a specific call by passing an options object to your get request and transforming your request headers:
const url = 'https://api.github.com/repos/xxx/releases/latest';
this.$axios.get(url, {
transformRequest: (data, headers) => {
delete headers.common['Authorization'];
return data;
}
})
.then((res) => {
this.latestRelease = res.data;
}).catch((error) => {
console.error(error);
})
As explained in their GitHub readme:
transformRequest allows changes to the request data before it is sent to the server.
This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'.
The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
FormData or Stream.
You may modify the headers object.
Creating a specific instance
You can create an instance of axios for different scenarios. This allows you to separate your axios calls that require an authorization header and those who don't. Each instance has its own 'global' options:
const axiosGitHub = axios.create({
baseURL: 'https://api.github.com/',
timeout: 1000,
headers: {}
});
// won't include the authorization header!
const data = await axiosGithub.get('repos/xxx/releases/latest');
You could use this answer to have several instances of axios: https://stackoverflow.com/a/67720641/8816585
Or you could also import a brand new axios and use it locally like this
<script>
import axios from 'axios'
export default {
methods: {
async callFakeApi() {
const result = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
console.log('result', result)
},
}
}
</script>
Axios interceptors as mentionned by Thatkookooguy are another solution!

How to Redirect From Backend To FrontEnd after Authentication

I Have My Backend (nodejs) running on another Port And My Frontend (React) Running On Another Port...So After After Sending Credentials To Backend And Authentication...How Can I Redirect To Front End ?
res.writeHead(302, {
Location: 'http://front-end.com:8888/some/path'
});
res.end();
If you specify the full url you can redirect to another port using NodeJS.
You may use a web API (Express JS) in nodejs to build web api and frontend any plainJS or modren libaries that will send HTTP request to backend. It will help more.
In your case, you'd better auth request through Ajax, that way you can return some kind of jwt token if you're using it, or trigger any other session related action on frontend on successful login.
From the frontend, on click of login button, you can create a function called login() something like below and redirect based on the response received from the backend
App.tsx
login() {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'test', password: 'password' })
};
fetch('http://localhost:5000', requestOptions)
.then(response => response.json())
.then(data => history.push('/someRoute'))
.catch(err => history.push('/login'));
}
in my case, I was handling the verifyEmail on the backend of a nest.js server.
How I was able to redirect to the frontend after handling the request, was to use the res.redirect from express.
#Get('verify/:token')
async verifyUser(#Param('token') token, #Res() response: Response): Promise<any> {
const redirectUrl = await this.authService.verifyUserEmail(token);
response.redirect(redirectUrl);
return {redirectUrl};
}
And, it worked like a charm.

Axios GET request sends empty req.body to server

I'm trying to make a GET request in my React app but Axios seems to send an empty request body for some reason. I know there's (most likely) nothing wrong in the backend as I'm able to make the requests perfectly fine with Insomnia. I've tried the following till now and none of the seem to work:
const response = await axios.get(URL, { email })
const response = await axios({
method: "get",
url: URL,
data: { email }
})
I'm using the express.json() middleware in the backend.
From the RFC 7231
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
So do not rely on body data for GET request and use appropriate HTTP method like POST, PUT etc.
Moreover, if you want to send Query params with your GET request, both code snippets you shared above will not work. Instead do it like below.
// using get method
const response = await axios.get(URL, {
params: {
ID: 12345
}
});
// using Axios API
const response = await axios({
method: "get",
url: URL,
params: {
ID: 12345
}
});

Downloading Excel data using Axios from Laravel backend is not working

I am developing a Web application using React JS for the front-end and Laravel for the back-end API. Now, what I am trying to do is I am trying to fetch the Excel data from the backend using Axios and then download the file.
This is my Laravel API controller action method.
function downloadExcel(Request $request)
{
//other code goes here
return Excel::create($left_photo->id . "-" . $right_photo->id, function($excel) use ($excel_data)
{
// Set the spreadsheet title, creator, and description
$excel->setTitle('Mapping points');
$excel->setCreator('Laravel')->setCompany('Memento');
$excel->setDescription('Mapping points file');
// Build the spreadsheet, passing in the payments array
$excel->sheet('sheet1', function($sheet) use ($excel_data)
{
$sheet->fromArray($excel_data, null, 'A1', false, false);
});
})->download('xlsx');
}
I fetch the data from the react js application using Axios like this.
export const getHttpClientFileDownload = (path) => {
let accessToken = localStorage.getItem("access_token");
return Axios({
url: getApiBaseEndpoint() + path,
method: 'GET',
responseType: 'blob', // important
headers : { 'api-version': API_VERSION, 'Authorization': 'Bearer ' + accessToken }
})
}
exportExcel()//this is the download medthod in the component
{
let path = 'photos/matching-points/excel?left_photo_id=' + this.props.leftImageId + "&right_photo_id=" + this.props.rightImageId;
//let path = "curator/event/" +this.props.match.params.id + "/details";
getHttpClientFileDownload(path)
.then((response) => {
alert('Everything is alright')
})
}
As you can see in the above code, if the request success, it should alert a message, "Everything is alright". But it is not alerting the message. But in the browser, it is successful.
When I make the request to the link that is returning just normal JSON response, it is alerting the message as expected. Only it is not working as expected when I make the request to the aforementioned Excel API.
I cannot use direct download link because I am doing some authorization on the server-side.
I had the same problem and found a solution as below.
Steps:
Call API to Laravel backend. Create file and store the same in local driver on server.
Return the file name to client.
Create a public route in Laravel (In web.php) to download files from Local storage. This route will delete the file after download it.
From client side, redirect the user to this public URL with the file name.
My code looks like this. I used fetch API.
Client side code:
const response = await fetch("my_server_url.com/api/createFile", {
method: 'GET',
headers: {
Authorization: "Bearer " + this.$store.state.AccessToken,
"X-Requested-With": "XMLHttpRequest",
},
});
if (!response.ok) {
console.log(response)
throw new Error("Something went wrong!");
}
const data = await response.json();
window.open("my_server_url.com/downloadFile/name="+data, '_blank');
Code in my_server_url.com/api/createFile route: (api.php)
public function createFile()
{
$file_name= date('YmdHis').rand();
Excel::store(new myExport(), $file_name.'.xlsx', 'local');
return response()->json($file_name.'.xlsx', 200);
}
Code in my_server_url.com/downloadFile/name={file_name} route: (web.php)
public function downloadFile($file_name)
{
return response()->download(Storage::path($file_name))->deleteFileAfterSend(true);
}
This way, you can check authorization and logic, but yet using the API. Also, make sure to add use Illuminate\Support\Facades\Storage; in laravel controller.
You can simply use window.open(path) to download files

Sending Audio Blob data from a React frontend to a Express backend

so I am running into an issue with sending audio(created in the browser) to my backend to be stored in a database. I am using React on the Front-end and using the Web Audio API to allow a user to create audio. When I send it, the backend just receives an empty object. Does anyone know how I can get the data to persist so that it can be stored in the database to be played later?
mediaRecorderOnStop() {
console.log("data available after MediaRecorder.stop() called.");
const clipName = prompt('Enter a name for your sound clip?', 'My unnamed clip')
//creating new blob object
const blob = new Blob(this.state.chunks, { 'type': 'audio/ogg; codecs=opus' });
//sending data to the backend
uploadDocumentRequest(this.props.createClip(blob, clipName))
this.setState({
chunks: []
})
console.log("recorder stopped");
}
//makes a api post request to server
export function uploadDocumentRequest(data) {
axios.post('/api/createAudio', data)
.then(response => console.log(uploadSuccess(response)))
.catch(error => console.log(uploadFail(error)));
}
Here is an image of my code
assuming you are sending JSON, you won't be able to send a binary data to the server.
you have to encode you data with multipart/formdata
for that you should construct FormData object which later can be sent via XHR or Fetch API
var fd = new FormData();
var fd.append('audio', blob);
fetch(apiUrl + '/api/createAudio', {
headers: { Accept: "application/json" },
method: "POST", body: fd
});
the request Content-Type: multipart/formdata header will be automatically set
you have to make sure that you web app is able to process multipart/formadata encoded requests

Resources