Skip to content

Commit 04f43fb

Browse files
committed
docs: expand Convex integration guide
1 parent fb81b41 commit 04f43fb

3 files changed

Lines changed: 406 additions & 5 deletions

File tree

  • docs-mintlify/guides/integrations/convex
  • docs/content/docs/(guides)/others
  • packages/template/src/integrations/convex/component

docs-mintlify/guides/integrations/convex/overview.mdx

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,27 @@ import { getConvexProvidersConfig } from "@stackframe/js"; // Vanilla JS
4242

4343
export default {
4444
providers: getConvexProvidersConfig({
45-
projectId: process.env.STACK_PROJECT_ID, // or: process.env.NEXT_PUBLIC_STACK_PROJECT_ID
45+
projectId: process.env.STACK_PROJECT_ID!,
4646
}),
4747
}
4848
```
4949

50+
Set `STACK_PROJECT_ID` in your Convex dashboard environment variables. Convex runs outside your Next.js process, so it reads the variables configured for the Convex deployment.
51+
52+
Next, update or create a file in `convex/convex.config.ts`:
53+
54+
```ts
55+
import { defineApp } from "convex/server";
56+
import stackAuthComponent from "@stackframe/js/convex.config"; // Vanilla JS
57+
// or: import stackAuthComponent from "@stackframe/react/convex.config"; // React
58+
// or: import stackAuthComponent from "@stackframe/stack/convex.config"; // Next.js
59+
60+
const app = defineApp();
61+
app.use(stackAuthComponent);
62+
63+
export default app;
64+
```
65+
5066
Then, update your Convex client to use Stack Auth:
5167

5268
```ts
@@ -77,4 +93,178 @@ export const myQuery = query({
7793
});
7894
```
7995

96+
### Partial users in Convex queries and mutations
97+
98+
`getPartialUser({ from: "convex", ctx })` reads the user identity that Convex verified from the Stack Auth JWT.
99+
100+
```ts
101+
import { query } from "./_generated/server";
102+
import { stackServerApp } from "../stack/server";
103+
104+
export const getUserInfo = query({
105+
handler: async (ctx) => {
106+
const user = await stackServerApp.getPartialUser({ from: "convex", ctx });
107+
108+
if (user === null) {
109+
return { signedIn: false };
110+
}
111+
112+
return {
113+
signedIn: true,
114+
user: {
115+
id: user.id,
116+
displayName: user.displayName,
117+
primaryEmail: user.primaryEmail,
118+
primaryEmailVerified: user.primaryEmailVerified,
119+
isAnonymous: user.isAnonymous,
120+
isMultiFactorRequired: user.isMultiFactorRequired,
121+
isRestricted: user.isRestricted,
122+
restrictedReason: user.restrictedReason,
123+
},
124+
};
125+
},
126+
});
127+
```
128+
129+
It returns:
130+
131+
```ts
132+
{
133+
id: string;
134+
displayName: string | null;
135+
primaryEmail: string | null;
136+
primaryEmailVerified: boolean;
137+
isAnonymous: boolean;
138+
isMultiFactorRequired: boolean;
139+
isRestricted: boolean;
140+
restrictedReason: string | null;
141+
}
142+
```
143+
144+
It does not include `teamId`, `selectedTeam`, or a teams list.
145+
146+
For mutations, read the user from `ctx` instead of accepting a user ID from the client:
147+
148+
```ts
149+
import { v } from "convex/values";
150+
import { mutation } from "./_generated/server";
151+
import { stackServerApp } from "../stack/server";
152+
153+
export const createNote = mutation({
154+
args: {
155+
text: v.string(),
156+
},
157+
handler: async (ctx, args) => {
158+
const user = await stackServerApp.getPartialUser({ from: "convex", ctx });
159+
160+
if (user === null) {
161+
throw new Error("User must be signed in to create a note.");
162+
}
163+
164+
return await ctx.db.insert("notes", {
165+
text: args.text,
166+
ownerUserId: user.id,
167+
});
168+
},
169+
});
170+
```
171+
172+
### Full users and teams
173+
174+
If you need Stack Auth team data, use the full Stack user from a Next.js route handler or a Convex action. Full users have APIs such as `user.selectedTeam` and `await user.listTeams()`.
175+
176+
In a Next.js route handler:
177+
178+
```ts
179+
const user = await stackServerApp.getUser({ tokenStore: request });
180+
const teams = user === null ? [] : await user.listTeams();
181+
```
182+
183+
```ts
184+
"use node";
185+
186+
import { action } from "./_generated/server";
187+
import { stackServerApp } from "../stack/server";
188+
189+
export const getFullStackUser = action({
190+
handler: async (ctx) => {
191+
const partialUser = await stackServerApp.getPartialUser({ from: "convex", ctx });
192+
193+
if (partialUser === null) {
194+
return null;
195+
}
196+
197+
const user = await stackServerApp.getUser(partialUser.id);
198+
199+
if (user === null) {
200+
throw new Error("Convex identity referenced a Stack Auth user that does not exist.");
201+
}
202+
203+
const teams = await user.listTeams();
204+
205+
return {
206+
id: user.id,
207+
selectedTeamId: user.selectedTeam?.id ?? null,
208+
teams: teams.map((team) => ({
209+
id: team.id,
210+
displayName: team.displayName,
211+
})),
212+
};
213+
},
214+
});
215+
```
216+
217+
### Calling Convex from a Next.js route handler
218+
219+
In Next.js route handlers, pass the Stack Auth token as the third argument to Convex's `fetchQuery`, `fetchMutation`, or `fetchAction` helpers:
220+
221+
```ts
222+
import { api } from "@/convex/_generated/api";
223+
import { stackServerApp } from "@/stack/server";
224+
import { fetchMutation, fetchQuery } from "convex/nextjs";
225+
import { NextRequest, NextResponse } from "next/server";
226+
227+
export async function GET(request: NextRequest) {
228+
const token = await stackServerApp.getConvexHttpClientAuth({
229+
tokenStore: request,
230+
});
231+
232+
if (token === "") {
233+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
234+
}
235+
236+
const userInfo = await fetchQuery(api.myFunctions.getUserInfo, {}, { token });
237+
238+
return NextResponse.json({ userInfo });
239+
}
240+
241+
export async function POST(request: NextRequest) {
242+
const token = await stackServerApp.getConvexHttpClientAuth({
243+
tokenStore: request,
244+
});
245+
246+
if (token === "") {
247+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
248+
}
249+
250+
const body = await request.json();
251+
252+
if (typeof body !== "object" || body === null || !("text" in body)) {
253+
return NextResponse.json({ error: "Missing text" }, { status: 400 });
254+
}
255+
256+
const text = Reflect.get(body, "text");
257+
258+
if (typeof text !== "string") {
259+
return NextResponse.json({ error: "text must be a string" }, { status: 400 });
260+
}
261+
262+
const noteId = await fetchMutation(api.myFunctions.createNote, { text }, { token });
263+
264+
return NextResponse.json({ noteId });
265+
}
266+
```
267+
268+
In `fetchQuery(api.myFunctions.getUserInfo, {}, { token })`, the second argument is the query args object, and the third argument is where the auth token goes.
269+
80270
You can find the production-ready template with Stack-Auth, Convex & Shadcn pre-configured [here on GitHub](https://github.com/developing-gamer/next-convex-stack-template).

0 commit comments

Comments
 (0)