Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-beds-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"uploadthing": minor
---

Add Convex Adapter
148 changes: 148 additions & 0 deletions docs/src/app/(docs)/backend-adapters/convex/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { docsMetadata } from "@/lib/utils";

export const metadata = docsMetadata({
title: "Convex",
description: "Adapter to integrate UploadThing into your Convex application",
category: "Backend Adapters",
});

# Getting started with Convex

> Added in `v7.8`

## Package Setup

### Install the package

```sh npm2yarn
npm install uploadthing
```

### Add env variables (in Convex dashboard)

<Note>
If you don't already have a uploadthing secret key, [sign
up](https://uploadthing.com/sign-in) and create one from the
[dashboard!](https://uploadthing.com/dashboard)
</Note>

```sh npm2yarn
npx convex env set UPLOADTHING_TOKEN=... # A token for interacting with the SDK
```

## Set Up A FileRouter

### Creating your first FileRoute

All files uploaded to uploadthing are associated with a FileRoute. The following
is a very minimalistic example, with a single FileRoute "imageUploader". Think
of a FileRoute similar to an endpoint, it has:

- Permitted types ["image", "video", etc]
- Max file size
- How many files are allowed to be uploaded
- (Optional) `input` validation to validate client-side data sent to the route
- (Optional) `middleware` to authenticate and tag requests
- `onUploadComplete` callback for when uploads are completed

To get full insight into what you can do with the FileRoutes, please refer to
the [File Router API](/file-routes).

```ts {{ title: "convex/uploadthing.ts" }}
"use node";

import crypto from "node:crypto";

import {
createInternalAction,
createUploadthing,
FileRouter,
} from "uploadthing/convex";

import { api } from "./_generated/api";

globalThis.crypto = crypto as Crypto;

const f = createUploadthing();

const router = {
// Define as many FileRoutes as you like, each with a unique routeSlug
imageUploader: f({
image: {
/**
* For full list of options and defaults, see the File Route API reference
* @see https://docs.uploadthing.com/file-routes#route-config
*/
maxFileSize: "4MB",
maxFileCount: 1,
},
}).onUploadComplete((data) => {
console.log("upload completed", data);
}),
} satisfies FileRouter;

export type OurFileRouter = typeof router;

export const handler = createInternalAction({ router });
```

### Create an API route using the FileRouter

<Note>
File path here doesn't matter, you can serve this from any route. We recommend
serving it from `/api/uploadthing`.
</Note>

```ts {{ title: "convex/http.ts" }}
import { httpRouter } from "convex/server";

import { createRouteHandler } from "uploadthing/convex-helpers";

import { internal } from "./_generated/api";

const http = httpRouter();

createRouteHandler({
http,
internalAction: internal.uploadthing.handler,
path: "/api/uploadthing",
});

export default http;
```

> See configuration options in
> [server API reference](/api-reference/server#create-route-handler)

### Use the FileRouter in your app

Client side usage differs ever so slightly from the fullstack framework setups
when using a separate backend server. You'll need to set the URL of your server
when you generate the components and helpers.

```tsx
import { generateUploadButton } from "@uploadthing/react";

export const UploadButton = generateUploadButton({
url: "https://your-server.com/api/uploadthing",
});
// ...
```

<Note>
Please note that you might need to setup some CORS rules on your server to
allow the client to make requests to the server.
</Note>

For the remaining usage, please refer to client side examples of the fullstack
framework guides:

- [Next.js](/getting-started/appdir#create-the-upload-thing-components)
- [Solid.js](/getting-started/solid#creating-the-upload-thing-components)
- [Vue](https://github.com/pingdotgg/uploadthing/tree/main/examples/backend-adapters/client-vue)
- [Svelte](/getting-started/svelte#creating-the-upload-thing-helpers)

or check out the full API reference:

- [`@uploadthing/react`](/api-reference/react)
- [`uploadthing/client`](/api-reference/client)
9 changes: 8 additions & 1 deletion docs/src/components/Libraries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Button } from "@/components/Button";
import { Heading } from "@/components/Heading";
import {
AstroIcon,
ConvexIcon,
ExpressIcon,
FastifyIcon,
H3Icon,
Expand Down Expand Up @@ -60,6 +61,12 @@ const frameworks = [
];

const backends = [
{
href: "/backend-adapters/convex",
name: "Convex",
description: "The open-source reactive database for app developers",
Logo: ConvexIcon,
},
{
href: "/backend-adapters/express",
name: "Express",
Expand Down Expand Up @@ -166,7 +173,7 @@ export function BackendAdapters() {
Not using a framework? We also have adapters for common backend
libraries.
</Prose>
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:max-w-none xl:grid-cols-2 dark:border-white/5">
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3 dark:border-white/5">
{backends.map((library) => (
<div key={library.name} className="flex gap-6">
<library.Logo className="size-12" />
Expand Down
24 changes: 24 additions & 0 deletions docs/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,27 @@ export function WinterCGIcon(props: IconProps) {
</svg>
);
}

export function ConvexIcon(props: IconProps) {
return (
<svg
viewBox="28 28 128 132"
xmlns="http://www.w3.org/2000/svg"
fill="none"
{...props}
>
<path
fill="#F3B01C"
d="M108.092 130.021c18.166-2.018 35.293-11.698 44.723-27.854-4.466 39.961-48.162 65.218-83.83 49.711-3.286-1.425-6.115-3.796-8.056-6.844-8.016-12.586-10.65-28.601-6.865-43.135 10.817 18.668 32.81 30.111 54.028 28.122Z"
/>
<path
fill="#8D2676"
d="M53.401 90.174c-7.364 17.017-7.682 36.94 1.345 53.336-31.77-23.902-31.423-75.052-.388-98.715 2.87-2.187 6.282-3.485 9.86-3.683 14.713-.776 29.662 4.91 40.146 15.507-21.3.212-42.046 13.857-50.963 33.555Z"
/>
<path
fill="#EE342F"
d="M114.637 61.855C103.89 46.87 87.069 36.668 68.639 36.358c35.625-16.17 79.446 10.047 84.217 48.807.444 3.598-.139 7.267-1.734 10.512-6.656 13.518-18.998 24.002-33.42 27.882 10.567-19.599 9.263-43.544-3.065-61.704Z"
/>
</svg>
);
}
1 change: 1 addition & 0 deletions docs/src/site-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const navigation: Array<NavGroup> = [
{
title: "Backend Adapters",
links: [
{ title: "Convex", href: "/backend-adapters/convex" },
{ title: "Express", href: "/backend-adapters/express" },
{ title: "Fastify", href: "/backend-adapters/fastify" },
{ title: "H3", href: "/backend-adapters/h3" },
Expand Down
9 changes: 9 additions & 0 deletions examples/minimal-convex/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Deployment used by `npx convex dev`
CONVEX_DEPLOYMENT=

NEXT_PUBLIC_CONVEX_URL=
NEXT_PUBLIC_CONVEX_SITE_URL=

# Required by the UploadThing Convex adapter — set these in Convex env (not this .env):
# pnpx convex env set UPLOADTHING_TOKEN='<your-uploadthing-token>'
# The adapter will 500/throw early if these are missing.
2 changes: 2 additions & 0 deletions examples/minimal-convex/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

.env.local
21 changes: 21 additions & 0 deletions examples/minimal-convex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Minimal Next.js App Router + Convex example for UploadThing

<a href="https://stackblitz.com/github/pingdotgg/uploadthing/tree/main/examples/minimal-convex">
<img height="64" src="https://github.com/pingdotgg/uploadthing/assets/51714798/45907a4e-aa64-401a-afb3-b6c6df6eb71f" />
</a>

## QuickStart

1. `pnpm i`
2. `pnpm dev:setup`
3. Add the `NEXT_PUBLIC_CONVEX_SITE_URL` to the .env file
4. Grab an API key from the UploadThing dashboard:
https://uploadthing.com/dashboard
5. `pnpm dev:convex`
6. `pnpx convex env set UPLOADTHING_TOKEN=<your-token>`
7. `pnpm dev`
8. Upload files!

## Further reference

Check out the docs at: https://docs.uploadthing.com/backend-adapters/convex
90 changes: 90 additions & 0 deletions examples/minimal-convex/convex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Welcome to your Convex functions directory!

Write your Convex functions here.
See https://docs.convex.dev/functions for more.

A query function that takes two arguments looks like:

```ts
// convex/myFunctions.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const myQueryFunction = query({
// Validators for arguments.
args: {
first: v.number(),
second: v.string(),
},

// Function implementation.
handler: async (ctx, args) => {
// Read the database as many times as you need here.
// See https://docs.convex.dev/database/reading-data.
const documents = await ctx.db.query("tablename").collect();

// Arguments passed from the client are properties of the args object.
console.log(args.first, args.second);

// Write arbitrary JavaScript here: filter, aggregate, build derived data,
// remove non-public properties, or create new objects.
return documents;
},
});
```

Using this query function in a React component looks like:

```ts
const data = useQuery(api.myFunctions.myQueryFunction, {
first: 10,
second: "hello",
});
```

A mutation function looks like:

```ts
// convex/myFunctions.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const myMutationFunction = mutation({
// Validators for arguments.
args: {
first: v.string(),
second: v.string(),
},

// Function implementation.
handler: async (ctx, args) => {
// Insert or modify documents in the database here.
// Mutations can also read from the database like queries.
// See https://docs.convex.dev/database/writing-data.
const message = { body: args.first, author: args.second };
const id = await ctx.db.insert("messages", message);

// Optionally, return a value from your mutation.
return await ctx.db.get(id);
},
});
```

Using this mutation function in a React component looks like:

```ts
const mutation = useMutation(api.myFunctions.myMutationFunction);
function handleButtonPress() {
// fire and forget, the most common way to use mutations
mutation({ first: "Hello!", second: "me" });
// OR
// use the result once the mutation has completed
mutation({ first: "Hello!", second: "me" }).then((result) =>
console.log(result),
);
}
```

Use the Convex CLI to push your functions to a deployment. See everything
the Convex CLI can do by running `npx convex -h` in your project root
directory. To learn more, launch the docs with `npx convex docs`.
40 changes: 40 additions & 0 deletions examples/minimal-convex/convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable */
/**
* Generated `api` utility.
*
* THIS CODE IS AUTOMATICALLY GENERATED.
*
* To regenerate, run `npx convex dev`.
* @module
*/

import type {
ApiFromModules,
FilterApi,
FunctionReference,
} from "convex/server";
import type * as http from "../http.js";
import type * as media from "../media.js";
import type * as uploadthing from "../uploadthing.js";

/**
* A utility for referencing Convex functions in your app's API.
*
* Usage:
* ```js
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
declare const fullApi: ApiFromModules<{
http: typeof http;
media: typeof media;
uploadthing: typeof uploadthing;
}>;
export declare const api: FilterApi<
typeof fullApi,
FunctionReference<any, "public">
>;
export declare const internal: FilterApi<
typeof fullApi,
FunctionReference<any, "internal">
>;
Loading