I'm trying to upload a file with the Google Drive api, and I have the metadata correct, and I want to ensure that the actual file contents make it there. I have a simple page setup that looks like this:
<div id="upload">
<h6>File Upload Operations</h6>
<input type="file" placeholder='file' name='fileToUpload'>
<button id='uploadFile'>Upload File</button>
</div>
and I have a the javascript setup where the user is prompted to sign in first, and then they can upload a file. Here's the code: (currently only uploads the file metadata....)
let uploadButton = document.getElementById('uploadFile');
uploadButton.onclick = uploadFile;
const uploadFile = () => {
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
console.dir(ftu);
gapi.client.drive.files.create({
'content-type': 'application/json;charset=utf-8',
uploadType: 'multipart',
name: ftu.name,
mimeType: ftu.type,
fields: 'id, name, kind'
}).then(response => {
console.dir(response);
console.log(`File: ${ftu.name} with MimeType of: ${ftu.type}`);
//Need code to upload the file contents......
});
};
First, I'm more familiar with the back end, so getting the file in bits from the <input type='file'> tag is a bit nebulous for me. On the bright side, the metadata is there. How can I get the file contents up to the api?
So According to some resources I've found in my three day search to get this going, the file simply cannot be uploaded via the gapi client. It must be uploaded through a true REST HTTP call. So let's use fetch!
const uploadFile = () => {
//initialize file data from the dom
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
let file = new Blob([ftu]);
//this is to ensure the file is in a format that can be understood by the API
gapi.client.drive.files.create({
'content-type': 'application/json',
uploadType: 'multipart',
name: ftu.name,
mimeType: ftu.type,
fields: 'id, name, kind, size'
}).then(apiResponse => {
fetch(`https://www.googleapis.com/upload/drive/v3/files/${response.result.id}`, {
method: 'PATCH',
headers: new Headers({
'Authorization': `Bearer ${gapi.client.getToken().access_token}`,
'Content-Type': ftu.type
}),
body: file
}).then(res => console.log(res));
}
The Authorization Header is assigned from calling the gapi.client.getToken().access_token function, and basically this takes the empty object from the response on the gapi call and calls the fetch api to upload the actual bits of the file!
In your situation, when you upload a file using gapi.client.drive.files.create(), the empty file which has the uploaded metadata is created. If my understanding is correct, how about this workaround? I have experienced the same situation with you. At that time, I used this workaround.
Modification points:
Retrieve access token using gapi.
File is uploaded using XMLHttpRequest.
Modified script:
Please modify the script in uploadFile().
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
var metadata = {
'name': ftu.name,
'mimeType': ftu.type,
};
var accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
form.append('file', ftu);
var xhr = new XMLHttpRequest();
xhr.open('post', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,kind');
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.responseType = 'json';
xhr.onload = () => {
console.log(xhr.response);
};
xhr.send(form);
Note:
In this modified script, it supposes that Drive API is enabled at API console and the access token can be used for uploading file.
About fields, you are using id,name,kind. So this sample also uses them.
Reference:
gapi
If I misunderstand your question or this workaround was not useful for your situation, I'm sorry.
Edit:
When you want to use fetch, how about this sample script?
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
var metadata = {
'name': ftu.name,
'mimeType': ftu.type,
};
var accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
form.append('file', ftu);
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,kind', {
method: 'POST',
headers: new Headers({'Authorization': 'Bearer ' + accessToken}),
body: form
}).then((res) => {
return res.json();
}).then(function(val) {
console.log(val);
});
With https://www.npmjs.com/package/#types/gapi.client.drive
const makeUploadUrl = (fileId: string, params: Record<string, boolean>) => {
const uploadUrl = new URL(
`https://www.googleapis.com/upload/drive/v3/files/${fileId}`
)
Object.entries({
...params,
uploadType: 'media',
}).map(([key, value]) => uploadUrl.searchParams.append(key, `${value}`))
return uploadUrl
}
const uploadDriveFile = async ({ file }: { file: File }) => {
const params = {
enforceSingleParent: true,
supportsAllDrives: true,
}
// create file handle
const { result } = await gapi.client.drive.files.create(params, {
// CAN'T have the upload type here!
name: file.name,
mimeType: file.type,
// any resource params you need...
driveId: process.env.DRIVE_ID,
parents: [process.env.FOLDER_ID],
})
// post the file data
await fetch(makeUploadUrl(result.id!, params), {
method: 'PATCH',
headers: new Headers({
Authorization: `Bearer ${gapi.client.getToken().access_token}`,
'Content-Type': file.type,
}),
body: file,
})
return result
})
}
Related
This is my function to upload the file to google drive:
async processFiles(files) {
const formData = new FormData()
formData.append("file", files[0])
formData.append("name", files[0].name)
formData.append("parents", this.currentFolder.folderId)
axios
.post("https://www.googleapis.com/upload/drive/v3/files", formData, {
headers: {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
},
the file is uploading to the general google drive and not to the specific folder (this.currentFolder.folderId).
What am I doing wrong here?
I tried some functions already and this is the only one that uploads file to the google drive.
In your script, how about the following modification?
Modified script:
async processFiles(files) {
const formData = new FormData();
formData.append("file", files[0]);
formData.append('metadata', new Blob([JSON.stringify({ name: files[0].name, parents: [this.currentFolder.folderId] })], { type: 'application/json' }));
axios
.post("https://www.googleapis.com/upload/drive/v3/files", formData, {
headers: { Authorization: `Bearer ${this.accessToken}` },
})
.then((response) => {
console.log(response.data)
})
.catch((error) => {
console.log(error)
})
},
When this script is run, the file of files[0] is uploaded to Google Drive with the file metadata of { name: files[0].name, parents: [this.currentFolder.folderId] }.
When a request is run by multipart/form-data with new FormData(), it is not required to set the content type of the request header. The content type including the boundary is automatically created.
In order to set the file metadata, I used formData.append('metadata', new Blob([JSON.stringify({ name: files[0].name, parents: [this.currentFolder.folderId] })], { type: 'application/json' }));.
Note:
In this modification, it supposes that files[0] is the valid file object and your access token can be used for uploading the file to Google Drive using Drive API. Please be careful about this.
References:
Files: create
Upload file data
parents is a list parameter, so you should provide as one in the form field.
Like this:
formData.append("parents[]", this.currentFolder.folderId)
Using raw HTML when I post a file to a flask server using the following I can access files from the flask request global:
<form id="uploadForm" action='upload_file' role="form" method="post" enctype=multipart/form-data>
<input type="file" id="file" name="file">
<input type=submit value=Upload>
</form>
In flask:
def post(self):
if 'file' in request.files:
....
When I try to do the same with Axios the flask request global is empty:
<form id="uploadForm" enctype="multipart/form-data" v-on:change="uploadFile">
<input type="file" id="file" name="file">
</form>
uploadFile: function (event) {
const file = event.target.files[0]
axios.post('upload_file', file, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
If I use the same uploadFile function above but remove the headers json from the axios.post method I get in the form key of my flask request object a csv list of string values (file is a .csv).
How can I get a file object sent via axios?
Add the file to a formData object, and set the Content-Type header to multipart/form-data.
var formData = new FormData();
var imagefile = document.querySelector('#file');
formData.append("image", imagefile.files[0]);
axios.post('upload_file', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
Sample application using Vue. Requires a backend server running on localhost to process the request:
var app = new Vue({
el: "#app",
data: {
file: ''
},
methods: {
submitFile() {
let formData = new FormData();
formData.append('file', this.file);
console.log('>> formData >> ', formData);
// You should have a server side REST API
axios.post('http://localhost:8080/restapi/fileupload',
formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function () {
console.log('SUCCESS!!');
})
.catch(function () {
console.log('FAILURE!!');
});
},
handleFileUpload() {
this.file = this.$refs.file.files[0];
console.log('>>>> 1st element in files array >>>> ', this.file);
}
}
});
https://codepen.io/pmarimuthu/pen/MqqaOE
If you don't want to use a FormData object (e.g. your API takes specific content-type signatures and multipart/formdata isn't one of them) then you can do this instead:
uploadFile: function (event) {
const file = event.target.files[0]
axios.post('upload_file', file, {
headers: {
'Content-Type': file.type
}
})
}
Sharing my experience with React & HTML input
Define input field
<input type="file" onChange={onChange} accept ="image/*"/>
Define onChange listener
const onChange = (e) => {
let url = "https://<server-url>/api/upload";
let file = e.target.files[0];
uploadFile(url, file);
};
const uploadFile = (url, file) => {
let formData = new FormData();
formData.append("file", file);
axios.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}).then((response) => {
fnSuccess(response);
}).catch((error) => {
fnFail(error);
});
};
const fnSuccess = (response) => {
//Add success handling
};
const fnFail = (error) => {
//Add failed handling
};
This works for me, I hope helps to someone.
var frm = $('#frm');
let formData = new FormData(frm[0]);
axios.post('your-url', formData)
.then(res => {
console.log({res});
}).catch(err => {
console.error({err});
});
this is my way:
var formData = new FormData(formElement);
// formData.append("image", imgFile.files[0]);
const res = await axios.post(
"link-handle",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
How to post file using an object in memory (like a JSON object):
import axios from 'axios';
import * as FormData from 'form-data'
async function sendData(jsonData){
// const payload = JSON.stringify({ hello: 'world'});
const payload = JSON.stringify(jsonData);
const bufferObject = Buffer.from(payload, 'utf-8');
const file = new FormData();
file.append('upload_file', bufferObject, "b.json");
const response = await axios.post(
lovelyURL,
file,
headers: file.getHeaders()
).toPromise();
console.log(response?.data);
}
There is an issue with Axios version 0.25.0 > to 0.27.2 where FormData object in a PUT request is not handled correctly if you have appended more than one field but is fine with one field containing a file, POST works fine.
Also Axios 0.25.0+ automatically sets the correct headers so there is no need to specify Content-Type.
For me the error was the actual parameter name in my controller... Took me a while to figure out, perhaps it will help someone. Im using Next.js / .Net 6
Client:
export const test = async (event: any) => {
const token = useAuthStore.getState().token;
console.log(event + 'the event')
if (token) {
const formData = new FormData();
formData.append("img", event);
const res = await axios.post(baseUrl + '/products/uploadproductimage', formData, {
headers: {
'Authorization': `bearer ${token}`
}
})
return res
}
return null
}
Server:
[HttpPost("uploadproductimage")]
public async Task<ActionResult> UploadProductImage([FromForm] IFormFile image)
{
return Ok();
}
Error here because server is expecting param "image" and not "img:
formData.append("img", event);
public async Task<ActionResult> UploadProductImage([FromForm] IFormFile image)
I've been trying to create a file using the Google APIs for Browser. I reused some of the code that I used to communicate with the api from NodeJS in the past and repurposed it for the browser.
const content = "this is some content";
const fileMetadata = {
name: "my-file.txt",
alt: "media",
};
const media = {
mimeType: "text/plain",
body: content,
};
const {
result: { id: fileId },
} = await gapi.client.drive.files.create({
resource: fileMetadata,
media: media,
fields: "id",
});
I typically get a successful response saying that my file was created.
However, when I try to get the contents of the file the body field is an empty string.
const { body } = await gapi.client.drive.files.get({
fileId: fileId,
});
console.log(body)
// ""
I think the request to create the file might not be formatted correctly but it works when it runs on the backend so I'm confused as to why it doesn't work in the browser.
You are mentioning that you are using the Google APIs for browser, and not node.js.
I would recommend to send the request directly against the Google REST API, as gapi.client.drive.create() appears to have problems sending the actual binary file (while sending metadata appears to work). Look here, for example: https://stackoverflow.com/a/53841879/7821823, https://stackoverflow.com/a/35182924/7821823 or https://stackoverflow.com/a/68595887/7821823
You can send the data as a blob and create the request using the FormData class.
async upload(blob, name, mimeType, parents = ["root"]) {
const metadata = { name, mimeType, parents };
const form = new FormData();
form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
form.append("file", blob);
return fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true", {
method: "POST",
headers: new Headers({ Authorization: `Bearer ${gapi.auth.getToken().access_token}` }),
body: form,
});
}
I have not tested if you can send a String instead of a Blob, but you can easily create a Blob from a String:
const content = "this is some content";
const blob = new Blob([content], { type: 'text/plain' });
The body of the file should be in the form of a stream. Not plain text.
var media = {
mimeType: 'text/plain',
body: fs.createReadStream('files/my-file.txt')
};
as for console.log(body)
Also file.get returns a file.resource File resource does not give you the option to see the contents of the file. Google Drive doesn't have the ability to read the file itself it can only tell you about the file.
I have a method that creates and upload a sheet to Google Drive. When a user clicks on a button that says "create a report", the report is created and uploaded to the user google drive. It works great. However, the response from Google after the sheet is being saved to google drive doesn't contain the sheet id nor the sheet URL. Users are saying that when they click on the "create report" button, they want the google sheet to open in a new tab after it's being saved to their google drive. They don't want to go in their drive to open the file manually. I was thinking that the response being return after the sheet is being uploaded would've contained at least the sheet id or the URL to the resource that's being created in google drive. Does anyone have an idea on how to accomplish what I am driving to do? I am using Google API to upload the file to the users drive.
Here is the response that get sent after the sheet is uploaded
"https://www.googleapis.com/upload/drive/v3/files?
uploadType=multipart&fields=id%2Cname%2Ckind", redirected: false,
status: 200, ok: true, …}
body: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: ""
type: "cors"
url: "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id%2Cname%2Ckind"
//Here is the code:
let metadata = {
name: excelFileName,
mimeType: "application/vnd.google-apps.spreadsheet",
};
let form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', excelFile);
let url = new URL('https://www.googleapis.com/upload/drive/v3/files');
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind' });
try {
let res = await fetch(url, {
method: 'POST',
headers: new Headers({ 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token }),
body: form
});
if (res.ok) {
console.log(gapi)
console.log(res)
alert(`"${excelFileName}" has successfully saved to your google drive!`);
} else {
console.error(`Encounter a problem saving excel file, ${excelFileName}, to google drive`);
}
} catch (ex) {
console.error(`Oops excel file for "${excelFileName}" wasn't saved. ${error}`)
}
When you uploaded a file, you want to retrieve the file ID and URLs of the uploaded file.
You want to achieve by modifying your current script.
If my understanding is correct, how about this modification?
Modification points:
In order to return the file ID and URLs, in this case, these fields are used.
exportLinks,id,kind,name,webContentLink,webViewLink
In order to retrieve the returned values from res, it uses res.json().
When above modification points are reflected to your script, it becomes as follows.
Modified script:
Please modify 2 parts of your script as follows.
From:
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind' });
To:
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'exportLinks,id,kind,name,webContentLink,webViewLink' });
And
From:
if (res.ok) {
console.log(gapi)
console.log(res)
To:
if (res.ok) {
res.json().then((value) => {
console.log(value)
});
References:
Files
Files: create
If I misunderstood your question and this was not the direction you want, I apologize.
You need to do 2 changes to obtain the URL from the file:
1) You have to add the webViewLink attribute [1] between your fields in the search parameters:
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind,webViewLink' });
This attribute is the link to open the file in the web.
2) The response body you get is a ReadableStream, you need to convert it to a json object that you can manipulate. This can be done using the json() function [2] to the response you’re getting, which will return the response body parsed to a Json object with the attributes: id, webViewLink, etc.
I tweaked and tested your code and worked as expected, showing the URL for the newly created file in the alert message:
let metadata = {
name: excelFileName,
mimeType: "application/vnd.google-apps.spreadsheet",
};
let form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', excelFile);
let url = new URL('https://www.googleapis.com/upload/drive/v3/files');
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind,webViewLink' });
try {
const res = await fetch(url, {
method: 'POST',
headers: new Headers({ 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token}),
body: form
});
//Convert response to json
const resJson = await res.json();
if (res.ok) {
console.log(gapi);
console.log(res);
console.log(resJson);
alert(`"${excelFileName}" has successfully saved to your google drive!: ` + resJson.webViewLink);
} else {
console.error(`Encounter a problem saving excel file, ${excelFileName}, to google drive:` + res.Json);
}
} catch (ex) {
console.error(`Oops excel file for "${excelFileName}" wasn't saved. ${ex}`)
}
All the code above inside a sync function of course, to be able to use the await statement.
[1] https://developers.google.com/drive/api/v3/reference/files#resource
[2] https://developer.mozilla.org/en-US/docs/Web/API/Body/json
I am trying to upload a file to a specific folder, but I am unable to do it.
Uploading is working correctly but it is not putting a file in a particular folder.
I am trying to do a Resumable upload with google drive rest version 3.
Assuming I already have a Folder ID.
First getting uploading URI :
uploadFileToDrive(name: string, content: string): Promise<Object> {
const url = `https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable`;
const accessToken = localStorage.getItem('accessToken');
let headers = new Headers({
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + accessToken,
});
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http
.post(url, { name: name, role: 'reader', type: 'anyone', 'parents': [{"id":parentId}] }, options)
.toPromise()
.then(response => this.gDriveUploadFile(content, response.headers.get('location')));
}
Second uploading media :
gDriveUploadFile(file, url): Promise<any> { //file and url we got from first func
console.log(file + " "+ url );
const accessToken = localStorage.getItem('accessToken');
let headers = new Headers({
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json; charset=UTF-8',
'X-Upload-Content-Type': file.type ,
});
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http.post(`${url}`, file, options) //call proper resumable upload endpoint and pass just file as body
.toPromise()
}
Also, I would like to know how I will be able to create a folder using google rest API in angular.