
API Integration Guide for v0 Applications
Connect your v0 applications to external APIs and services effectively.
Modern applications rely on APIs for data and functionality. Learn how to integrate external APIs effectively in v0 projects with proper error handling, caching, and security.
API Types
Understanding different API architectures helps you choose the right approach:
REST APIs: Traditional HTTP-based APIs using standard methods (GET, POST, PUT, DELETE). Best for simple CRUD operations and widely supported.
GraphQL: Query language for APIs allowing clients to request exactly the data they need. Ideal for complex data requirements and reducing over-fetching.
WebSockets: Bidirectional communication for real-time features. Perfect for chat applications, live updates, and collaborative tools.
Server-Sent Events (SSE): One-way communication from server to client. Great for live notifications and streaming data.
Making API Calls
Use modern approaches for reliable API communication:
Fetch API with Error Handling:
```typescript
// lib/api/client.ts
export class APIError extends Error {
constructor(
message: string,
public status: number,
public data?: any
) {
super(message)
this.name = 'APIError'
}
}
export async function fetchAPI<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
})
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new APIError(
error.message || 'API request failed',
response.status,
error
)
}
return response.json()
}
```
SWR for Data Fetching:
```typescript
import useSWR from 'swr'
import { fetchAPI } from '@/lib/api/client'
export function useUser(id: string) {
const { data, error, isLoading, mutate } = useSWR(
`/users/${id}`,
fetchAPI,
{
revalidateOnFocus: false,
revalidateOnReconnect: true,
dedupingInterval: 5000,
onError: (error) => {
console.error('Failed to fetch user:', error)
},
}
)
return {
user: data,
isLoading,
isError: error,
mutate, // Manually revalidate
}
}
// Usage in component
function UserProfile({ userId }: { userId: string }) {
const { user, isLoading, isError } = useUser(userId)
if (isLoading) return <Skeleton />
if (isError) return <ErrorMessage />
return <Profile user={user} />
}
```
React Query for Advanced Caching:
```typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function usePosts() {
return useQuery({
queryKey: ['posts'],
queryFn: () => fetchAPI('/posts'),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
})
}
export function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (newPost: Post) => fetchAPI('/posts', {
method: 'POST',
body: JSON.stringify(newPost),
}),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
}
```
Server-Side API Calls
Leverage Next.js features for secure, efficient API calls:
API Routes:
```typescript
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams
const page = searchParams.get('page') || '1'
const response = await fetch(`https://api.example.com/posts?page=${page}`, {
headers: {
'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,
},
next: { revalidate: 60 }, // Cache for 60 seconds
})
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch posts' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
// Validate input
if (!body.title || !body.content) {
return NextResponse.json(
{ error: 'Title and content are required' },
{ status: 400 }
)
}
const response = await fetch('https://api.example.com/posts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
const data = await response.json()
return NextResponse.json(data, { status: 201 })
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create post' },
{ status: 500 }
)
}
}
```
Server Components:
```typescript
// app/posts/page.tsx
async function getPosts() {
const response = await fetch('https://api.example.com/posts', {
headers: {
'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,
},
next: { revalidate: 3600 }, // Revalidate every hour
})
if (!response.ok) {
throw new Error('Failed to fetch posts')
}
return response.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
{posts.map((post: Post) => (
<PostCard key={post.id} post={post} />
))}
</div>
)
}
```
Server Actions:
```typescript
// app/actions/posts.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
const response = await fetch('https://api.example.com/posts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, content }),
})
if (!response.ok) {
throw new Error('Failed to create post')
}
revalidatePath('/posts')
return response.json()
}
```
Authentication
Handle API authentication securely:
API Keys:
```typescript
// Store in environment variables
const API_KEY = process.env.API_SECRET_KEY
// Include in requests
fetch('https://api.example.com/data', {
headers: {
'X-API-Key': API_KEY,
},
})
```
OAuth Tokens:
```typescript
// lib/api/auth.ts
export async function getAccessToken() {
const response = await fetch('https://oauth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.OAUTH_CLIENT_ID!,
client_secret: process.env.OAUTH_CLIENT_SECRET!,
}),
})
const data = await response.json()
return data.access_token
}
// Use token in requests
const token = await getAccessToken()
fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${token}`,
},
})
```
JWT Tokens:
```typescript
import { SignJWT, jwtVerify } from 'jose'
export async function createToken(payload: any) {
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(secret)
}
export async function verifyToken(token: string) {
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
try {
const { payload } = await jwtVerify(token, secret)
return payload
} catch (error) {
return null
}
}
```
Error Handling
Implement robust error handling:
Retry Logic:
```typescript
async function fetchWithRetry<T>(
url: string,
options?: RequestInit,
maxRetries = 3
): Promise<T> {
let lastError: Error
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json()
} catch (error) {
lastError = error as Error
// Don't retry on client errors (4xx)
if (error instanceof Error && error.message.startsWith('HTTP 4')) {
throw error
}
// Wait before retrying (exponential backoff)
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000))
}
}
}
throw lastError!
}
```
Fallback UI:
```typescript
function DataDisplay() {
const { data, error, isLoading } = useSWR('/api/data', fetcher, {
fallbackData: cachedData, // Use cached data while revalidating
onError: (error) => {
// Log error to monitoring service
console.error('API Error:', error)
},
})
if (error) {
return (
<div className="text-center py-8">
<p className="text-destructive">Failed to load data</p>
<Button onClick={() => mutate()}>Retry</Button>
</div>
)
}
if (isLoading) return <Skeleton />
return <DataTable data={data} />
}
```
Rate Limiting
Respect API limits and implement client-side throttling:
Request Throttling:
```typescript
import { debounce } from 'es-toolkit'
// Debounce search requests
const debouncedSearch = debounce(async (query: string) => {
const results = await fetchAPI(`/search?q=${query}`)
setResults(results)
}, 300)
// Usage
<input onChange={(e) => debouncedSearch(e.target.value)} />
```
Request Queue:
```typescript
class RequestQueue {
private queue: Array<() => Promise<any>> = []
private processing = false
private maxConcurrent = 3
private active = 0
async add<T>(request: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await request()
resolve(result)
} catch (error) {
reject(error)
}
})
this.process()
})
}
private async process() {
if (this.processing || this.active >= this.maxConcurrent) return
const request = this.queue.shift()
if (!request) return
this.processing = true
this.active++
await request()
this.active--
this.processing = false
if (this.queue.length > 0) {
this.process()
}
}
}
const queue = new RequestQueue()
// Usage
queue.add(() => fetchAPI('/endpoint1'))
queue.add(() => fetchAPI('/endpoint2'))
```
Proper API integration ensures your application is reliable, performant, and maintainable.
Need Help with Your v0 Project?
Our team of v0 experts is ready to help you build amazing applications with cutting-edge AI technology.
Get in Touch