Uploading files from React Native or Expo
Say you’re building a mobile React Native app that let’s users take voice notes. You’ve built a cool UI that let’s you record audio by following the guide from Expo. The API to record files gives you a URI to the file on disk. (If you’re building a photo app and need image uploads, the ImagePicker API also gives you a URI to your images on disk)
You’ve already setup the Convex mutation to generate an upload URL and the mutation to store the app specific metadata. (Jump to the bottom to see the mutations used in this example)
Now that you have the URI to the file on disk and the mutations to store it on the backend, how do you actually upload the file from the computer to the Convex remote servers?
Here’s a snippet that makes this work in your React Native app. Follow along in the comments to understand each step:
1// Setup Convex mutations for use in your React component
2const generateUploadUrl = useMutation(api.recordings.generateUploadUrl);
3const saveRecording = useMutation(api.recordings.saveRecording);
4
5// Method called to actually upload the file
6async function uploadAndSaveRecording(uri: string) {
7 // 1. Get upload url from Convex.
8 // This is the first mutation from the Convex docs for uploading.
9 const postUrl = await generateUploadUrl();
10
11 // 2. Get the file from the URI pointing to a local file
12 const fileData = await fetch(uri);
13 // Basic error handling to ensure the file is valid.
14 if (!fileData.ok) {
15 console.error("Error loading file.", fileData);
16 return;
17 }
18
19 // 3. Get the file contents to upload
20 const blob = await fileData.blob();
21
22 // 4. Set up error handling for your upload
23 try {
24 // 5. Actually send the file contents to Convex
25 const result = await fetch(postUrl, {
26 method: "POST",
27 // Note: Use the right mime type.
28 // iOS and Android use mp4 for audio recordings.
29 headers: { "Content-Type": "audio/mp4" },
30 body: blob,
31 });
32 // MOAR ERROR HANDLING!
33 if (!result.ok) {
34 // Note: You may actually want to inform the user of this.
35 console.log("Failed to upload.");
36 return;
37 }
38
39 // 6. Once successfully uploaded get the storageId
40 const { storageId } = await result.json();
41
42 // 7. Store custom metadata associated with this recording in Convex
43 const uploadResult = await saveRecording({ storageId, author: user.id });
44 } catch (err) {
45 // Note: You may actually want to inform the user of this.
46 console.error("Failed to upload.", err);
47 throw err;
48 }
49}
50
That’s it! To confirm everything is working, check the file uploads section of your Convex Dashboard.
And check the data table to make sure the application specific metadata is saved.
If it’s all there, then it’s working! Great work!
For completeness here are are the Convex mutations that match the upload code above.
1import { v } from "convex/values";
2import { mutation } from "./_generated/server";
3import { internal } from "./_generated/api";
4
5// Generate the URL that you will actually upload the file to
6export const generateUploadUrl = mutation(async (ctx) => {
7 return await ctx.storage.generateUploadUrl();
8});
9
10// Mutation to save custom metadata for the file
11export const saveRecording = mutation({
12 args: { storageId: v.id("_storage"), author: v.string() },
13 handler: async (ctx, args) => {
14 const recId = await ctx.db.insert("recordings", {
15 storageId: args.storageId,
16 author: args.author,
17 format: "audio",
18 });
19 ctx.scheduler.runAfter(0, internal.transcript.transcribe, { recId });
20 return recId;
21 },
22});
23
Convex is the sync platform with everything you need to build your full-stack project. Cloud functions, a database, file storage, scheduling, search, and realtime updates fit together seamlessly.