Skip to content

Commit 631fd77

Browse files
committed
add Nango OAuth integration guide
1 parent 774007e commit 631fd77

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed

docs/docs.json

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

docs/guides/frameworks/nango.mdx

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
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 — this replaces the old public key approach. 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+
const response = await fetch("https://api.nango.dev/connect/sessions", {
78+
method: "POST",
79+
headers: {
80+
Authorization: `Bearer ${process.env.NANGO_SECRET_KEY}`,
81+
"Content-Type": "application/json",
82+
},
83+
body: JSON.stringify({
84+
end_user: { id: userId },
85+
}),
86+
});
87+
88+
const { data } = await response.json();
89+
return NextResponse.json({ token: data.token });
90+
}
91+
```
92+
93+
Then add a connect button to your UI that fetches the token and opens the Nango OAuth flow:
94+
95+
```tsx app/page.tsx
96+
"use client";
97+
98+
import Nango from "@nangohq/frontend";
99+
100+
export default function Page() {
101+
async function connectGitHub() {
102+
// Get a short-lived session token from your backend
103+
const sessionRes = await fetch("/api/nango-session", {
104+
method: "POST",
105+
headers: { "Content-Type": "application/json" },
106+
body: JSON.stringify({ userId: "user_123" }), // replace with your actual user ID
107+
});
108+
const { token } = await sessionRes.json();
109+
110+
const nango = new Nango({ connectSessionToken: token });
111+
const result = await nango.auth("github");
112+
113+
// result.connectionId is what you pass to your task
114+
await fetch("/api/analyze-prs", {
115+
method: "POST",
116+
headers: { "Content-Type": "application/json" },
117+
body: JSON.stringify({
118+
connectionId: result.connectionId,
119+
repo: "triggerdotdev/trigger.dev",
120+
}),
121+
});
122+
}
123+
124+
return <button onClick={connectGitHub}>Analyze my PRs</button>;
125+
}
126+
```
127+
128+
<Note>
129+
The session token expires after 30 minutes and is used only to open the OAuth popup — it is not stored. The `connectionId` returned by `nango.auth()` is the stable identifier you pass to future task runs to retrieve the user's access token.
130+
</Note>
131+
132+
</Step>
133+
</Steps>
134+
135+
## Step 2: Create the Trigger.dev task
136+
137+
Install the required packages:
138+
139+
```bash
140+
npm install @nangohq/node @anthropic-ai/sdk
141+
```
142+
143+
Create the task:
144+
145+
<CodeGroup>
146+
147+
```ts trigger/analyze-prs.ts
148+
import { task } from "@trigger.dev/sdk";
149+
import { Nango } from "@nangohq/node";
150+
import Anthropic from "@anthropic-ai/sdk";
151+
152+
const nango = new Nango({ secretKey: process.env.NANGO_SECRET_KEY! });
153+
const anthropic = new Anthropic();
154+
155+
export const analyzePRs = task({
156+
id: "analyze-prs",
157+
run: async (payload: { connectionId: string; repo: string }) => {
158+
const { connectionId, repo } = payload;
159+
160+
// Fetch a fresh access token from Nango. It handles refresh automatically.
161+
// Use the exact integration slug from your Nango dashboard, e.g. "github-getting-started"
162+
const connection = await nango.getConnection("<your-integration-slug>", connectionId);
163+
164+
if (connection.credentials.type !== "OAUTH2") {
165+
throw new Error(`Unexpected credentials type: ${connection.credentials.type}`);
166+
}
167+
168+
const accessToken = connection.credentials.access_token;
169+
// Call the GitHub API on behalf of the user
170+
const response = await fetch(
171+
`https://api.github.com/repos/${repo}/pulls?state=open&per_page=20`,
172+
{
173+
headers: {
174+
Authorization: `Bearer ${accessToken}`,
175+
Accept: "application/vnd.github.v3+json",
176+
},
177+
}
178+
);
179+
180+
if (!response.ok) {
181+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
182+
}
183+
184+
const prs = await response.json();
185+
186+
if (prs.length === 0) {
187+
return { summary: "No open pull requests found.", prCount: 0 };
188+
}
189+
190+
// Use Claude to summarize what's being worked on
191+
const prList = prs
192+
.map(
193+
(pr: { number: number; title: string; user: { login: string }; body: string | null }) =>
194+
`#${pr.number} by @${pr.user.login}: ${pr.title}\n${pr.body?.slice(0, 200) ?? ""}`
195+
)
196+
.join("\n\n");
197+
198+
const message = await anthropic.messages.create({
199+
model: "claude-opus-4-6",
200+
max_tokens: 1024,
201+
messages: [
202+
{
203+
role: "user",
204+
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}`,
205+
},
206+
],
207+
});
208+
209+
const summary = message.content[0].type === "text" ? message.content[0].text : "";
210+
211+
return { summary, prCount: prs.length };
212+
},
213+
});
214+
```
215+
216+
</CodeGroup>
217+
218+
## Step 3: Create the API route
219+
220+
Add a route handler that receives the `connectionId` from your frontend and triggers the task:
221+
222+
```ts app/api/analyze-prs/route.ts
223+
import { tasks } from "@trigger.dev/sdk";
224+
import { analyzePRs } from "@/trigger/analyze-prs";
225+
import { NextResponse } from "next/server";
226+
227+
export async function POST(req: Request) {
228+
const { connectionId, repo } = await req.json();
229+
230+
if (!connectionId || !repo) {
231+
return NextResponse.json({ error: "Missing connectionId or repo" }, { status: 400 });
232+
}
233+
234+
const handle = await tasks.trigger<typeof analyzePRs>("analyze-prs", {
235+
connectionId,
236+
repo,
237+
});
238+
239+
return NextResponse.json(handle);
240+
}
241+
```
242+
243+
## Step 4: Set environment variables
244+
245+
Add the following to your `.env` file. Trigger.dev dev mode reads from `.env`, not `.env.local`, so keep task-related secrets there:
246+
247+
```bash
248+
NANGO_SECRET_KEY= # From Nango dashboard → Environment → Secret key
249+
TRIGGER_SECRET_KEY= # From Trigger.dev dashboard → API keys
250+
ANTHROPIC_API_KEY= # From Anthropic console
251+
```
252+
253+
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.
254+
255+
## Test it
256+
257+
<Steps titleSize="h3">
258+
<Step title="Start your dev servers">
259+
260+
```bash
261+
npm run dev
262+
npx trigger.dev@latest dev
263+
```
264+
265+
</Step>
266+
<Step title="Connect GitHub and trigger the task">
267+
Open your app, click **Analyze my PRs**, and complete the GitHub OAuth flow. The task will be triggered automatically.
268+
</Step>
269+
<Step title="Watch the run in Trigger.dev">
270+
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.
271+
</Step>
272+
</Steps>
273+
274+
<Check>
275+
Your task is now fetching a fresh GitHub token from Nango, calling the GitHub API on behalf of the
276+
user, and using Claude to summarize their open PRs. No token storage or refresh logic required.
277+
</Check>
278+
279+
## Next steps
280+
281+
- **Reuse the `connectionId`**: Once a user has connected, store their `connectionId` and pass it in future task payloads. No need to re-authenticate.
282+
- **Add retries**: If the GitHub API returns a transient error, Trigger.dev [retries](/errors-retrying) will handle it automatically.
283+
- **Switch providers**: The same pattern works for any Nango-supported API. Change `"github"` to `"hubspot"`, `"slack"`, `"notion"`, or any other provider.
284+
- **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)