How to upload a file through Apollo Server v4 and graphql-upload? - apollo-server

im using Apollo server v4 with expressjs and NEXTJS for my Frontend. What I want is to create a Mutation like uploadFile using upload-graphql to upload a file to my Backend Server.
upload type
export interface Upload {
filename: string;
mimetype: string;
encoding: string;
createReadStream: () => Stream;
}
my mutation:
#Mutation(() => Boolean)
async uploadFile(
#Arg("picture", () => GraphQLUpload)
{ createReadStream, filename }: Upload
): Promise<boolean> {
return new Promise(async (resolve, reject) =>
createReadStream()
.pipe(createWriteStream(__dirname + `/../../../images/${filename}`))
.on("finish", () => resolve(true))
.on("error", () => reject(false))
);
}
}
but the problem is, whenever I upload something like a photo,
I get ApolloError: Argument Validation Error .
I think, there is no way to upload through new Version of Apollo server (v4) yet, but if anyone knows about it, please help me !
my dependencies
"dependencies":{
"#apollo/server": "^4.3.1",
"express": "^4.18.2",
"graphql": "^16.6.0",
"graphql-fields": "^2.0.3",
"graphql-middleware": "^6.1.33",
"graphql-scalars": "^1.20.1",
"graphql-shield": "^7.6.5",
"graphql-upload": "14",
"type-graphql": "2.0.0-beta.1", },
I tried to upload a file on my frontend nextjs - apollo-upload-client.
and front-end upload Scenario
It's just a hobby project, and I know there are alternatives to kind of use other approach to upload files => like extra endpoint api or using like s3, but I want to get my files uploaded through graphql.

Related

Parsing formdata from React using Serverless and API Gateway

I'm trying to upload a file and send data from a React frontend to a S3 bucket using an API Gateway/ Lambda function setup using the Serverless framework and I've been struggling with it for the last couple of days.
From the frontend I am using axios and creating a formdata to send a post request to the API like the following:
let formData = new FormData();
formData.append('imageFile', selectedImage);
formData.append('itemId', clubIdRef.current.value);
formData.append('itemDescription', itemDescRef.current.value);
axios.post(
baesURL+"/item/create", formData,
{headers: {
'Content-Type': 'multipart/form-data'
}}
).then((response) => {
console.log("response" + response)
console.log("response.data" + response.data)
})
Appending string attributes to the formdata feels off but the only way I could find to send data and an image at the same time was like the above.
Then to receive this data in the backend I've been using lambda-multipart-parser like the following:
const createItem = async (event) => {
const result = await multipartParser.parse(event);
const imageFile = result.imageFile;
const itemDescription = result.itemDescription;
where the result console logs as:
{
files: [],
imageFile: '[object File]',
itemId: '12',
itemDescription: "Description"
}
I can then store the imageFile successfully in S3 and generate the URL. Next, I create an Item object with the S3 url and id and description to store in dynamoDB. Everything works fine but when I open the S3 url the file is corrupted and just opens as a grey box instead of the actual image I uploaded.
This is how I am uploading the file using the s3 sdk
const AWS = require("aws-sdk");
const s3 = new AWS.S3();
const params = {
Bucket: BUCKET_NAME,
Key: `images/${directoryPath}/${id}.png`,
Body: imageFile,
ContentType: "image/png",
ACL : "public-read"
}
uploadResult = await s3.putObject(params).promise();
These are the things I've tried but still don't have any success uploading the correct image to my S3 bucket:
Looking and changing the BinaryMediaType of the API gateway but I can't find the settings under the API...
Tried using aws-lambda-multipart-parser but still wasn't able to add multipart/form-data binary media type and parse the full form data correctly
I know that I could first try to send a request directly from React to S3 to upload the image using aws-sdk in react to get a preSignedURL and attach that URL and make a POST request to my API Gateway simply parse the event.body without having to use a multipart form parser, but I want to avoid sending multiple requests if needed and handle everything in the backend.
Any suggestions would be highly appreciated!
It is quite hard to understand where is the problem with given context.
We have no idea which image format you are uploading, no idea how you store this image to S3.
My answer will try to cover these missing informations as it is a common mistake on S3 uploads.
S3 files are stored and returned with given ContentType.
You might check your S3 file's ContentType on AWS console.
Console > S3 > Select object (image) > Metadata > ContentType
I will suppose that image format is PNG and image data is correct and might be posted to S3 as is (from result).
S3Service.ts
import AWS, {S3} from "aws-sdk";
import {PutObjectRequest} from "aws-sdk/clients/s3";
import {PutObjectResponse} from "aws-sdk/clients/mediastoredata";
AWS.config.update({region: 'eu-west-3' });
const s3: S3 = new AWS.S3();
export class S3Service {
public static async putImage(key: string, data: string, contentType: string): Promise<PutObjectResponse> {
const s3Params: PutObjectRequest = {
Bucket: process.env.S3_BUCKET,
Key: key,
Body: data,
ContentType: contentType // <== I draw your attention here
}
return await s3.putObject(s3Params).promise()
}
}
index.ts
import { S3Service } from "service/aws/s3-service";
await S3Service.putImage(result.itemId + ".png", result.imageFile, "image/png");
A common mistake, which I assume might be the cause of your problem, is to forget content-type resulting in incorrect download format.

how to get the file link after successfully uploading in minio

I am using minio to manage the files
const getMinioClient = () => {
const minioClient = new Minio.Client({
endPoint: '127.0.0.1',
port: 9000,
useSSL: false,
accessKey: 'minioadmin',
secretKey: 'minioadmin'
});
return minioClient;
};
uploadFile(bucketName, newFileName, localFileLocation,metadata={}) {
return new Promise((resolve, reject) => {
const minioClient = getMinioClient();
//'application/octet-stream'
minioClient.fPutObject(bucketName, newFileName, localFileLocation, metadata , (err, etag) => {
if (err) return reject(err);
return resolve(etag);
});
});
}
with the following code I can upload the file, after successfully uploading it returns me only with etag, but I want to get the download link, how would I get it directly without searching the filename again.
You won't be able to get something like Public URL/Link for accessing images unless you ask for it to manually generate a time limited download URL using something like:
https://min.io/docs/minio/linux/reference/minio-mc/mc-share-download.html#generate-a-url-to-download-object-s
One workaround is to let nginx directly access the location you are uploading your files to:
https://gist.github.com/harshavardhana/f05b60fe6f96803743f38bea4b565bbf
After you have successfully written your file with your code above, you can use presignedUrl method to generate the link to your image.
An example for Javascript is here: https://min.io/docs/minio/linux/developers/javascript/API.html#presignedUrl:~:text=//%20presigned%20url%20for%20%27getObject%27%20method.%0A//%20expires%20in%20a%20day.%0AminioClient.presignedUrl(%27GET%27%2C%20%27mybucket%27%2C%20%27hello.txt%27%2C%2024*60*60%2C%20function(err%2C%20presignedUrl)%20%7B%0A%20%20if%20(err)%20return%20console.log(err)%0A%20%20console.log(presignedUrl)%0A%7D)
In any case you have to set an expiration time. Here or you set a very long time, which is suitable to your app or if you have a backend, require the images from Frontend through the backend with the getObject method: getObject(bucketName, objectName, getOpts[, callback]).
https://min.io/docs/minio/linux/developers/javascript/API.html#presignedUrl:~:text=getObject(bucketName%2C%20objectName%2C%20getOpts%5B%2C%20callback%5D)
If you have only a few number of static images to show in your app, (which are not uploaded by your app), you can also create the links manually with tme minio client or from the Minio-UI.

add API key to url

Hi I'm build a wildfire app tracker with react using the nasa API it works in development by using the url directly witch is https://eonet.sci.gsfc.nasa.gov/api/v2.1/events
But when I deploy it. It does not get the data. I obviously need a api key witch I have, but how do I implement it in the url above ?
here is my code..
useEffect(() => {
const fetchEvents = async () => {
setLoading(true)
const res = await fetch('https://eonet.sci.gsfc.nasa.gov/api/v2.1/events')
const {events} = await res.json()
setEventData(events)
setLoading(false)
}
fetchEvents()
// eslint-disable-next-line
}, [])
You could try to create a .env file in which you can set URLS as
REACT_APP_PUBLIC_URL=https://eonet.sci.gsfc.nasa.gov/api/v2.1/events
and then in your app component import as
fetch(process.env.REACT_APP_PUBLIC_URL)

Upload multiple files from flutter to Laravel backend

I'm trying to upload multiple files from flutter to laravel backend, I use the file_picker package to select the files from the phone, then I use Dio package to send them as formData to the backend, here's my function
Future<String> _uploadFiles(List<PlatformFile> paths) async {
print(paths);
FormData formData = FormData.fromMap({
"user_id": "a287f309-ff1a-4352-9576-770bb72192bb",
"service_type": "ترجمة",
"translator_level": "معتمد",
"translating_speed": "السرعة العادية",
"summary": "ملخص الطلب",
"lang_from": "العربية",
"lang_to": "الانجليزية",
"specialization": "تقني",
"files": paths
});
try {
Response response = await Dio()
.post('http://192.168.43.139:8000/api/orders/create', data: formData);
print(response.data);
} on DioError catch (e) {
print(e.response.data);
}
}
But I'm getting an error regarding the files uploading, the laravel backend can't parse the files which are send by this way, anyone know who to solve this problem ?

nuxtjs middleware rest API raw data requests

I have build nuxtjs example with built in rest API requests with middleware technique. In index.js of my middleware section I put some code :
export default {
handler(req, res) {
res.write('Everything ok!')
console.log(req);
console.log(res);
res.end()
},
path: '/test15'
}
When I call http://ip:port/test15/?cmd=somecmd&param=testparam
In console.log I get params data in log, everything nice. No matter which method used, post or get, it also fixed in log.
The problem is when I try to send raw data (ex json) in request body or form data. I can`t see them in any log output.
So question is, is it possible to send some data in such requests via middleware ?
Thanks!
middleware in nuxt is a sandwich for internal routes aka client side. For your question serverMiddleware is the answer that work on the server side. You can checkout more here
Quick example:
In your nuxt.config.js file add like below
serverMiddleware: [
{ path: '/api/subscribe', handler: '~/api/subscribe' }
],
Then create an api folder you can create subscribe.js file to add relative api route.
import express from 'express'
const app = express()
app.get('/subscribe', async (req, res) => {
res.send('love to the world');
})
export default {
path: '/api',
handler: app
}

Resources