Skip to content

Commit c37bb9a

Browse files
authored
docs: add Nango OAuth integration guide (#3262)
Adds a guide showing how to use Nango to make authenticated API calls inside a Trigger.dev task, using GitHub + Claude as a concrete example.
1 parent f2b1b76 commit c37bb9a

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@
369369
"guides/ai-agents/claude-code-trigger",
370370
"guides/frameworks/drizzle",
371371
"guides/frameworks/prisma",
372+
"guides/frameworks/nango",
372373
"guides/frameworks/sequin",
373374
{
374375
"group": "Supabase",

docs/guides/frameworks/nango.mdx

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
---
2+
title: "Nango OAuth with Trigger.dev"
3+
sidebarTitle: "Nango OAuth guide"
4+
description: "Use Nango to authenticate API calls inside a Trigger.dev task, no token management required."
5+
icon: "key"
6+
---
7+
8+
[Nango](https://www.nango.dev/) handles OAuth for 250+ APIs, storing and automatically refreshing access tokens on your behalf. This makes it a natural fit for Trigger.dev tasks that need to call third-party APIs on behalf of your users.
9+
10+
In this guide you'll build a task that:
11+
12+
1. Receives a Nango `connectionId` from your frontend
13+
2. Fetches a fresh GitHub access token from Nango inside the task
14+
3. Calls the GitHub API to retrieve the user's open pull requests
15+
4. Uses Claude to summarize what's being worked on
16+
17+
This pattern works for any API Nango supports. Swap GitHub for HubSpot, Slack, Notion, or any other provider.
18+
19+
## Prerequisites
20+
21+
- A Next.js project with [Trigger.dev installed](/guides/frameworks/nextjs)
22+
- A [Nango](https://app.nango.dev/) account
23+
- An [Anthropic](https://console.anthropic.com/) API key
24+
25+
## How it works
26+
27+
```mermaid
28+
sequenceDiagram
29+
participant User
30+
participant Frontend
31+
participant API as Next.js API
32+
participant TD as Trigger.dev task
33+
participant Nango
34+
participant GH as GitHub API
35+
participant Claude
36+
37+
User->>Frontend: Clicks "Analyze my PRs"
38+
Frontend->>API: POST /api/nango-session
39+
API->>Nango: POST /connect/sessions (secret key)
40+
Nango-->>API: session token (30 min TTL)
41+
API-->>Frontend: session token
42+
Frontend->>Nango: OAuth connect (frontend SDK + session token)
43+
Nango-->>Frontend: connectionId
44+
Frontend->>API: POST /api/analyze-prs { connectionId, repo }
45+
API->>TD: tasks.trigger(...)
46+
TD->>Nango: getConnection(connectionId)
47+
Nango-->>TD: access_token
48+
TD->>GH: GET /repos/:repo/pulls
49+
GH-->>TD: open pull requests
50+
TD->>Claude: Summarize PRs
51+
Claude-->>TD: Summary
52+
```
53+
54+
## Step 1: Connect GitHub in Nango
55+
56+
<Steps titleSize="h3">
57+
<Step title="Create a GitHub integration in Nango">
58+
1. In your [Nango dashboard](https://app.nango.dev/), go to **Integrations** and click **Set up new integration**.
59+
2. Search for **GitHub** and select GitHub (User OAuth).
60+
3. Create and add a test connection
61+
</Step>
62+
<Step title="Add the Nango frontend SDK">
63+
Install the Nango frontend SDK in your Next.js project:
64+
65+
```bash
66+
npm install @nangohq/frontend
67+
```
68+
69+
The frontend SDK requires a short-lived **connect session token** issued by your backend. Add an API route that creates the session:
70+
71+
```ts app/api/nango-session/route.ts
72+
import { NextResponse } from "next/server";
73+
74+
export async function POST(req: Request) {
75+
const { userId } = await req.json();
76+
77+
if (!userId || typeof userId !== "string") {
78+
return NextResponse.json({ error: "Missing or invalid userId" }, { status: 400 });
79+
}
80+
81+
const response = await fetch("https://api.nango.dev/connect/sessions", {
82+
method: "POST",
83+
headers: {
84+
Authorization: `Bearer ${process.env.NANGO_SECRET_KEY}`,
85+
"Content-Type": "application/json",
86+
},
87+
body: JSON.stringify({
88+
end_user: { id: userId },
89+
}),
90+
});
91+
92+
if (!response.ok) {
93+
const text = await response.text();
94+
console.error("Nango error:", response.status, text);
95+
return NextResponse.json({ error: text }, { status: response.status });
96+
}
97+
98+
const { data } = await response.json();
99+
return NextResponse.json({ token: data.token });
100+
}
101+
```
102+
103+
Then add a connect button to your UI that fetches the token and opens the Nango OAuth flow:
104+
105+
```tsx app/page.tsx
106+
"use client";
107+
108+
import Nango from "@nangohq/frontend";
109+
110+
export default function Page() {
111+
async function connectGitHub() {
112+
// Get a short-lived session token from your backend
113+
const sessionRes = await fetch("/api/nango-session", {
114+
method: "POST",
115+
headers: { "Content-Type": "application/json" },
116+
body: JSON.stringify({ userId: "user_123" }), // replace with your actual user ID
117+
});
118+
const { token } = await sessionRes.json();
119+
120+
const nango = new Nango({ connectSessionToken: token });
121+
// Use the exact integration slug from your Nango dashboard
122+
const result = await nango.auth("<your-integration-slug>");
123+
124+
// result.connectionId is what you pass to your task
125+
await fetch("/api/analyze-prs", {
126+
method: "POST",
127+
headers: { "Content-Type": "application/json" },
128+
body: JSON.stringify({
129+
connectionId: result.connectionId,
130+
repo: "triggerdotdev/trigger.dev",
131+
}),
132+
});
133+
}
134+
135+
return <button onClick={connectGitHub}>Analyze my PRs</button>;
136+
}
137+
```
138+
139+
</Step>
140+
</Steps>
141+
142+
## Step 2: Create the Trigger.dev task
143+
144+
Install the required packages:
145+
146+
```bash
147+
npm install @nangohq/node @anthropic-ai/sdk
148+
```
149+
150+
Create the task:
151+
152+
<CodeGroup>
153+
154+
```ts trigger/analyze-prs.ts
155+
import { task } from "@trigger.dev/sdk";
156+
import { Nango } from "@nangohq/node";
157+
import Anthropic from "@anthropic-ai/sdk";
158+
159+
const nango = new Nango({ secretKey: process.env.NANGO_SECRET_KEY! });
160+
const anthropic = new Anthropic();
161+
162+
export const analyzePRs = task({
163+
id: "analyze-prs",
164+
run: async (payload: { connectionId: string; repo: string }) => {
165+
const { connectionId, repo } = payload;
166+
167+
// Fetch a fresh access token from Nango. It handles refresh automatically.
168+
// Use the exact integration slug from your Nango dashboard, e.g. "github-getting-started"
169+
const connection = await nango.getConnection("<your-integration-slug>", connectionId);
170+
171+
if (connection.credentials.type !== "OAUTH2") {
172+
throw new Error(`Unexpected credentials type: ${connection.credentials.type}`);
173+
}
174+
175+
const accessToken = connection.credentials.access_token;
176+
// Call the GitHub API on behalf of the user
177+
const response = await fetch(
178+
`https://api.github.com/repos/${repo}/pulls?state=open&per_page=20`,
179+
{
180+
headers: {
181+
Authorization: `Bearer ${accessToken}`,
182+
Accept: "application/vnd.github.v3+json",
183+
},
184+
}
185+
);
186+
187+
if (!response.ok) {
188+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
189+
}
190+
191+
const prs = await response.json();
192+
193+
if (prs.length === 0) {
194+
return { summary: "No open pull requests found.", prCount: 0 };
195+
}
196+
197+
// Use Claude to summarize what's being worked on
198+
const prList = prs
199+
.map(
200+
(pr: { number: number; title: string; user: { login: string }; body: string | null }) =>
201+
`#${pr.number} by @${pr.user.login}: ${pr.title}\n${pr.body?.slice(0, 200) ?? ""}`
202+
)
203+
.join("\n\n");
204+
205+
const message = await anthropic.messages.create({
206+
model: "claude-opus-4-6",
207+
max_tokens: 1024,
208+
messages: [
209+
{
210+
role: "user",
211+
content: `Here are the open pull requests for ${repo}. Give a concise summary of what's being worked on, grouped by theme where possible.\n\n${prList}`,
212+
},
213+
],
214+
});
215+
216+
const summary = message.content[0].type === "text" ? message.content[0].text : "";
217+
218+
return { summary, prCount: prs.length };
219+
},
220+
});
221+
```
222+
223+
</CodeGroup>
224+
225+
## Step 3: Create the API route
226+
227+
Add a route handler that receives the `connectionId` from your frontend and triggers the task:
228+
229+
```ts app/api/analyze-prs/route.ts
230+
import { analyzePRs } from "@/trigger/analyze-prs";
231+
import { NextResponse } from "next/server";
232+
233+
export async function POST(req: Request) {
234+
const { connectionId, repo } = await req.json();
235+
236+
if (!connectionId || !repo) {
237+
return NextResponse.json({ error: "Missing connectionId or repo" }, { status: 400 });
238+
}
239+
240+
const handle = await analyzePRs.trigger({ connectionId, repo });
241+
242+
return NextResponse.json(handle);
243+
}
244+
```
245+
246+
## Step 4: Set environment variables
247+
248+
Add the following to your `.env.local` file:
249+
250+
```bash
251+
NANGO_SECRET_KEY= # From Nango dashboard → Environment → Secret key
252+
TRIGGER_SECRET_KEY= # From Trigger.dev dashboard → API keys
253+
ANTHROPIC_API_KEY= # From Anthropic console
254+
```
255+
256+
Add `NANGO_SECRET_KEY` and `ANTHROPIC_API_KEY` as [environment variables](/deploy-environment-variables) in your Trigger.dev project too. These are used inside the task at runtime.
257+
258+
## Test it
259+
260+
<Steps titleSize="h3">
261+
<Step title="Start your dev servers">
262+
263+
```bash
264+
npm run dev
265+
npx trigger.dev@latest dev
266+
```
267+
268+
</Step>
269+
<Step title="Connect GitHub and trigger the task">
270+
Open your app, click **Analyze my PRs**, and complete the GitHub OAuth flow. The task will be triggered automatically.
271+
</Step>
272+
<Step title="Watch the run in Trigger.dev">
273+
Open your [Trigger.dev dashboard](https://cloud.trigger.dev/) and navigate to **Runs** to see the task execute. You'll see the PR count and Claude's summary in the output.
274+
</Step>
275+
</Steps>
276+
277+
<Check>
278+
Your task is now fetching a fresh GitHub token from Nango, calling the GitHub API on behalf of the
279+
user, and using Claude to summarize their open PRs. No token storage or refresh logic required.
280+
</Check>
281+
282+
## Next steps
283+
284+
- **Reuse the `connectionId`**: Once a user has connected, store their `connectionId` and pass it in future task payloads. No need to re-authenticate.
285+
- **Add retries**: If the GitHub API returns a transient error, Trigger.dev [retries](/errors-retrying) will handle it automatically.
286+
- **Switch providers**: The same pattern works for any Nango-supported API. Change `"github"` to `"hubspot"`, `"slack"`, `"notion"`, or any other provider.
287+
- **Stream the analysis**: Use [Trigger.dev Realtime](/realtime/overview) to stream Claude's response back to your frontend as it's generated.

0 commit comments

Comments
 (0)